Skip to content

Latest commit

 

History

History
997 lines (708 loc) · 32.8 KB

File metadata and controls

997 lines (708 loc) · 32.8 KB

七、操纵字符串

在本章中,我们将介绍:

  • 更改大小写和不区分大小写的比较
  • 使用正则表达式匹配字符串
  • 使用正则表达式搜索和替换字符串
  • 使用类似 printf 的安全函数格式化字符串
  • 替换和擦除字符串
  • 用两个迭代器表示一个字符串
  • 使用对字符串类型的引用

介绍

这一整章致力于改变、搜索和表示字符串的不同方面。我们将看到如何使用 Boost 库轻松完成一些常见的字符串相关任务。这一章足够轻松;它处理非常常见的字符串操作任务。那么,让我们开始吧!

更改大小写和不区分大小写的比较

这是一个相当常见的任务。我们有两个非 Unicode 或 ANSI 字符串:

#include <string> 
std::string str1 = "Thanks for reading me!"; 
std::string str2 = "Thanks for reading ME!"; 

我们需要以不区分大小写的方式比较它们。有很多方法可以做到这一点,让我们看看 Boost 的。

准备好

std::string基础知识是我们这里需要的全部。

怎么做...

以下是进行不区分大小写比较的不同方法:

  1. 最简单的一个是:
#include <boost/algorithm/string/predicate.hpp> 

const bool solution_1 = (
     boost::iequals(str1, str2)
);
  1. 使用 Boost 谓词和标准库方法:
#include <boost/algorithm/string/compare.hpp> 
#include <algorithm> 

const bool solution_2 = (
    str1.size() == str2.size() && std::equal(
        str1.begin(),
        str1.end(),
        str2.begin(),
        boost::is_iequal()
    )
);
  1. 制作两个字符串的小写副本:
#include <boost/algorithm/string/case_conv.hpp> 

void solution_3() {
    std::string str1_low = boost::to_lower_copy(str1);
    std::string str2_low = boost::to_lower_copy(str2);
    assert(str1_low == str2_low);
}
  1. 制作原始字符串的大写副本:
#include <boost/algorithm/string/case_conv.hpp> 

void solution_4() {
    std::string str1_up = boost::to_upper_copy(str1);
    std::string str2_up = boost::to_upper_copy(str2);
    assert(str1_up == str2_up);
}
  1. 将原始字符串转换为小写:
#include <boost/algorithm/string/case_conv.hpp> 

void solution_5() {
    boost::to_lower(str1);
    boost::to_lower(str2);
    assert(str1 == str2);
}

它是如何工作的...

第二种方法并不明显。在第二种方法中,我们比较字符串的长度。如果它们具有相同的长度,我们使用boost::is_iequal谓词的一个实例逐个字符地比较字符串。boost::is_iequal谓词以不区分大小写的方式比较两个字符。

The Boost.StringAlgorithm library uses i in the name of a method or class, if this method is case-insensitive. For example, boost::is_iequal, boost::iequals, boost::is_iless, and others.

还有更多...

处理案例的Boost.StringAlgorithm库的每个功能和功能对象都接受std::locale。默认情况下(在我们的例子中),方法和类使用默认构造的std::locale。如果我们经常处理字符串,那么构造一个std::locale变量并将其传递给所有方法可能是一个很好的优化。另一个很好的优化是通过std::locale::classic()使用 C 语言环境(如果您的应用逻辑允许的话):

  // On some platforms std::locale::classic() works 
  // faster than std::locale().
  boost::iequals(str1, str2, std::locale::classic()); 

Nobody forbids you to use both optimizations.

可惜 C++ 17 没有Boost.StringAlgorithm开始的字符串函数。所有的算法都是快速可靠的,所以不要害怕在代码中使用它们。

请参见

  • 助推字符串算法库的官方文档可以在http://boost.org/libs/algorithm/string找到
  • 参见安德烈·亚历山德雷斯库和赫伯·萨特的《C++ 编码标准》一书,了解如何用几行代码创建不区分大小写的字符串

使用正则表达式匹配字符串

让我们做点有用的事吧!用户的输入必须使用一些正则表达式来检查,这是很常见的情况。问题是有很多正则表达式语法,使用一种语法编写的表达式没有被其他语法很好地处理。另一个问题是长正则表达式不太容易写。

因此,在这个食谱中,我们将编写一个程序,支持不同的正则表达式语法,并检查输入字符串是否与指定的正则表达式匹配。

入门指南

这个食谱需要标准库的基础知识。正则表达式语法的知识可能会有所帮助。

需要根据boost_regex库链接示例。

怎么做...

这个正则表达式匹配器示例由main()函数中的几行代码组成:

  1. 为了实现它,我们需要以下头:
#include <boost/regex.hpp> 
#include <iostream> 
  1. 在程序开始时,我们需要输出可用的正则表达式语法:
int main() { 
    std::cout  
        << "Available regex syntaxes:\n" 
        << "\t[0] Perl\n" 
        << "\t[1] Perl case insensitive\n" 
        << "\t[2] POSIX extended\n" 
        << "\t[3] POSIX extended case insensitive\n" 
        << "\t[4] POSIX basic\n" 
        << "\t[5] POSIX basic case insensitive\n\n" 
        << "Choose regex syntax: "; 
  1. 现在,根据选择的语法正确设置标志:
    boost::regex::flag_type flag;
    switch (std::cin.get()) 
    {
    case '0': flag = boost::regex::perl;
        break;

    case '1': flag = boost::regex::perl|boost::regex::icase;
        break;

    case '2': flag = boost::regex::extended;
        break;

    case '3': flag = boost::regex::extended|boost::regex::icase;
        break;

    case '4': flag = boost::regex::basic;
        break;

    case '5': flag = boost::regex::basic|boost::regex::icase;
        break;
    default:
        std::cout << "Incorrect number of regex syntax. Exiting...\n";
        return 1;
    }

    // Disabling exceptions.
    flag |= boost::regex::no_except;
  1. 我们现在在循环中请求正则表达式模式:
    // Restoring std::cin.
    std::cin.ignore();
    std::cin.clear();

    std::string regex, str;
    do {
        std::cout << "Input regex: ";
        if (!std::getline(std::cin, regex) || regex.empty()) {
            return 0;
        }

        // Without `boost::regex::no_except`flag this
        // constructor may throw.
        const boost::regex e(regex, flag);
        if (e.status()) {
            std::cout << "Incorrect regex pattern!\n";
            continue;
        }
  1. 循环得到一个String to match::
        std::cout << "String to match: ";
        while (std::getline(std::cin, str) && !str.empty()) {
  1. 对其应用正则表达式并输出结果:
            const bool matched = boost::regex_match(str, e);
            std::cout << (matched ? "MATCH\n" : "DOES NOT MATCH\n");
            std::cout << "String to match: ";
        } // end of `while (std::getline(std::cin, str))`
  1. 我们将通过恢复std::cin并请求新的正则表达式模式来完成我们的示例:
        // Restoring std::cin.
        std::cin.ignore();
        std::cin.clear();
    } while (1);
} // int main() 

现在,如果我们运行前面的示例,我们将获得以下输出:

 Available regex syntaxes:
 [0] Perl
 [1] Perl case insensitive
 [2] POSIX extended
 [3] POSIX extended case insensitive
 [4] POSIX basic
 [5] POSIX basic case insensitive
Choose regex syntax: 0
 Input regex: (\d{3}[#-]){2}
 String to match: 123-123#
 MATCH
 String to match: 312-321-
 MATCH
 String to match: 21-123-
 DOES NOT MATCH
 String to match: ^Z
 Input regex: \l{3,5}
 String to match: qwe
 MATCH
 String to match: qwert
 MATCH
 String to match: qwerty
 DOES NOT MATCH
 String to match: QWE
 DOES NOT MATCH
 String to match: ^Z

 Input regex: ^Z
 Press any key to continue . . .

它是如何工作的...

所有匹配都由boost::regex类完成。它构造了一个能够进行正则表达式解析和编译的对象。使用flag输入变量将附加配置选项传递给类。

如果正则表达式不正确,boost::regex抛出异常。如果通过了boost::regex::no_except标志,它会报告一个错误,在status()调用中返回一个非零值(就像我们的例子一样):

        if (e.status()) {
            std::cout << "Incorrect regex pattern!\n";
            continue;
        }

这将导致:

Input regex: (incorrect regex(
Incorrect regex pattern!

正则表达式匹配通过调用boost::regex_match函数来完成。如果匹配成功,则返回true。额外的标志可以传递给regex_match,但是为了示例的简洁,我们避免了它们的使用。

还有更多...

C++ 11 包含了几乎所有的Boost.Regex类和标志。它们可以在std::命名空间的<regex>头中找到(而不是boost::)。官方文档提供了关于 C++ 11 和Boost.Regex的区别的信息。它还包含一些性能指标,表明Boost.Regex速度很快。一些标准库存在性能问题,因此在 Boost 和标准库版本之间进行明智的选择。

请参见

  • 通过正则表达式搜索和替换字符串的方法将为您提供更多关于Boost.Regex用法的信息
  • 您也可以考虑官方文档来获取更多关于标志、性能度量、正则表达式语法和 C++ 11 一致性的信息,这些信息可以在http://boost.org/libs/regex获得

使用正则表达式搜索和替换字符串

我妻子非常喜欢正则表达式匹配字符串的食谱。但是,她想要更多,并告诉我,在我推广食谱以便能够根据正则表达式匹配替换部分输入字符串之前,我不会得到任何食物。

好了,来了。每个匹配的子表达式(括号中正则表达式的一部分)必须获得一个从 1 开始的唯一数字;这个数字将用于创建一个新的字符串。

更新后的程序应该是这样工作的:

 Available regex syntaxes:
 [0] Perl
 [1] Perl case insensitive
 [2] POSIX extended
 [3] POSIX extended case insensitive
 [4] POSIX basic
 [5] POSIX basic case insensitive
 Choose regex syntax: 0
 Input regex: (\d)(\d)
 String to match: 00
 MATCH: 0, 0,
 Replace pattern: \1#\2
 RESULT: 0#0
 String to match: 42
 MATCH: 4, 2,
 Replace pattern: ###\1-\1-\2-\1-\1###
 RESULT: ###4-4-2-4-4###

准备好

我们将重用通过正则表达式匹配字符串的代码。建议在拿到这个之前先看一下。

需要根据boost_regex库链接一个示例。

怎么做...

这个食谱是基于前一个食谱的代码。让我们看看必须改变什么:

  1. 不应包含额外的标题。但是,我们需要一个额外的字符串来存储替换模式:
    std::string regex, str, replace_string;
  1. 我们将boost::regex_match替换为boost::regex_find,并输出匹配结果:
        std::cout << "String to match: ";
        while (std::getline(std::cin, str) && !str.empty()) {
            boost::smatch results;
            const bool matched = regex_search(str, results, e);
            if (matched)  {
                std::cout << "MATCH: ";
                std::copy(
                    results.begin() + 1, 
                    results.end(), 
                    std::ostream_iterator<std::string>(std::cout, ", ")
                );
  1. 之后,我们需要获取替换模式并应用它:
                std::cout << "\nReplace pattern: ";
                if (
                        std::getline(std::cin, replace_string)
                        && !replace_string.empty())
                {
                    std::cout << "RESULT: " << 
                        boost::regex_replace(str, e, replace_string)
                    ; 
                } else {
                    // Restoring std::cin.
                    std::cin.ignore();
                    std::cin.clear();
                }
            } else { // `if (matched) `
                std::cout << "DOES NOT MATCH";
            }

就这样!每个人都很开心,我也吃饱了。

它是如何工作的...

boost::regex_search函数不仅返回truefalse值(与boost::regex_match函数不同),还存储匹配的零件。我们使用以下结构输出匹配的零件:

    std::copy( 
        results.begin() + 1,  
        results.end(),  
        std::ostream_iterator<std::string>( std::cout, ", ") 
    ); 

请注意,我们通过跳过第一个结果(results.begin() + 1)来输出结果,这是因为results.begin()包含整个正则表达式匹配。

boost::regex_replace函数完成所有替换并返回修改后的字符串。

还有更多...

regex_*函数有不同的变体,有些接收双向迭代器而不是字符串,有些向迭代器提供输出。

boost::smatchboost::match_results<std::string::const_iterator>typedef。如果您正在使用一些其他的双向迭代器而不是std::string::const_iterator,您应该使用您的双向迭代器的类型作为boost::match_results的模板参数。

match_results有一个格式函数,所以我们可以用它来调整我们的例子,而不是:

std::cout << "RESULT: " << boost::regex_replace(str, e, replace_string); 

我们可以使用以下内容:

std::cout << "RESULT: " << results.format(replace_string); 

顺便说一下,replace_string支持多种格式:

Input regex: (\d)(\d)
 String to match: 12
 MATCH: 1, 2,
 Replace pattern: $1-$2---$&---$$
 RESULT: 1-2---12---$

这个配方中的所有类和函数都存在于 C++ 11 的<regex>头的std::命名空间中。

请参见

Boost.Regex的官方文档将在http://boost.org/libs/regex为您提供更多关于性能、C++ 11 标准兼容性和正则表达式语法的示例和信息。正则表达式匹配字符串食谱将告诉你Boost.Regex的基本知识。

使用类似 printf 的安全函数格式化字符串

printf系列功能是对安全的威胁。允许用户将自己的字符串作为类型并格式化说明符是一个非常糟糕的设计。那么,当需要用户定义的格式时,我们该怎么办呢?下面这个类的std::string to_string(const std::string& format_specifier) const;成员函数该如何实现?

class i_hold_some_internals 
{
    int i;
    std::string s;
    char c;
    // ...
}; 

准备好

标准库的基础知识对这个食谱来说绰绰有余。

怎么做...

我们希望允许用户为字符串指定他们自己的输出格式:

  1. 为了以安全的方式做到这一点,我们需要以下标题:
#include <boost/format.hpp>
  1. 现在,我们为用户添加一些注释:
    // `fmt` parameter may contain the following:
    // $1$ for outputting integer 'i'.
    // $2$ for outputting string 's'.
    // $3$ for outputting character 'c'.
    std::string to_string(const std::string& fmt) const {
  1. 是时候让所有部分都发挥作用了:
        boost::format f(fmt);
        unsigned char flags = boost::io::all_error_bits;
        flags ^= boost::io::too_many_args_bit;
        f.exceptions(flags);
        return (f % i % s % c).str();
    }

仅此而已。看看这段代码:

int main() {
    i_hold_some_internals class_instance;

    std::cout << class_instance.to_string(
        "Hello, dear %2%! "
        "Did you read the book for %1% %% %3%\n"
    );

    std::cout << class_instance.to_string(
        "%1% == %1% && %1%%% != %1%\n\n"
    );
}

想象一下class_instance有一个等于100的成员is有一个等于"Reader"的成员,还有一个等于'!'的成员c。然后,程序将输出以下内容:

 Hello, dear Reader! Did you read the book for 100 % !
 100 == 100 && 100% != 100

它是如何工作的...

boost::format类接受指定结果字符串格式的字符串。使用operator%将参数传递给boost::format。指定字符串格式的值%1%%2%%3%%4%等被传递给boost::format的参数替换。

对于格式字符串包含的参数少于传递给boost::format的参数的情况,我们也禁用例外:

    boost::format f(format_specifier);
    unsigned char flags = boost::io::all_error_bits;
    flags ^= boost::io::too_many_args_bit;

这样做是为了允许像这样的一些格式:

    // Outputs 'Reader'.
    std::cout << class_instance.to_string("%2%\n\n");

还有更多...

如果格式不正确会发生什么?

没什么可怕的,一个异常被抛出:

    try {
        class_instance.to_string("%1% %2% %3% %4% %5%\n");
        assert(false);
    } catch (const std::exception& e) {
        // boost::io::too_few_args exception is catched.
        std::cout << e.what() << '\n';
    }

前面的代码片段将以下几行输出到控制台:

 boost::too_few_args: format-string referred to more arguments than
    were passed

C++ 17 没有std::formatBoost.Format图书馆不是一个很快的图书馆。尽量不要在性能关键部分大量使用它。

请参见

官方文档包含更多关于Boost.Format库性能的信息。更多类似扩展 printf 格式的示例和文档可在http://boost.org/libs/format.获得

替换和擦除字符串

我们需要删除字符串中的某些内容,替换字符串的一部分,或者删除一些子字符串的第一次或最后一次出现的情况非常常见。标准库允许我们在这方面做更多的工作,但是它通常涉及太多的代码编写。

我们在变化案例和不区分大小写比较配方中看到Boost.StringAlgorithm库正在运行。让我们看看,当我们需要修改一些字符串时,如何使用它来简化我们的生活:

#include <string> 
const std::string str = "Hello, hello, dear Reader."; 

准备好

这个例子需要 C++ 的基础知识。

怎么做...

该配方显示了Boost.StringAlgorithm库中不同的字符串擦除和替换方法是如何工作的:

  1. 擦除需要#include <boost/algorithm/string/erase.hpp>标题:
#include <boost/algorithm/string/erase.hpp>

void erasing_examples() {
    namespace ba = boost::algorithm;
    using std::cout;

    cout << "\n erase_all_copy :" << ba::erase_all_copy(str, ",");
    cout << "\n erase_first_copy:" << ba::erase_first_copy(str, ",");
    cout << "\n erase_last_copy :" << ba::erase_last_copy(str, ",");
    cout << "\n ierase_all_copy :" << ba::ierase_all_copy(str, "hello");
    cout << "\n ierase_nth_copy :" << ba::ierase_nth_copy(str, ",", 1);
}

该代码输出以下内容:

 erase_all_copy   :Hello hello dear Reader.
 erase_first_copy :Hello hello, dear Reader.
 erase_last_copy  :Hello, hello dear Reader.
 ierase_all_copy   :, , dear Reader.
 ierase_nth_copy  :Hello, hello dear Reader.
  1. 更换需要<boost/algorithm/string/replace.hpp>表头:
#include <boost/algorithm/string/replace.hpp>

void replacing_examples() {
    namespace ba = boost::algorithm;
    using std::cout;

    cout << "\n replace_all_copy :" 
        << ba::replace_all_copy(str, ",", "!");

    cout << "\n replace_first_copy :"
        << ba::replace_first_copy(str, ",", "!");

    cout << "\n replace_head_copy :"
        << ba::replace_head_copy(str, 6, "Whaaaaaaa!");
}

该代码输出以下内容:

 replace_all_copy :Hello! hello! dear Reader.
 replace_first_copy :Hello! hello, dear Reader.
 replace_head_copy :Whaaaaaaa! hello, dear Reader.

它是如何工作的...

所有的例子都是自我记录的。唯一不明显的是replace_head_copy功能。它接受要替换的字节数作为第二个参数,接受替换字符串作为第三个参数。因此,在前面的例子中,Hello被替换为Whaaaaaaa!

还有更多...

还有就地修改字符串的方法。它们只是不会在_copy结束,然后返回void。所有不区分大小写的方法(以i开头的方法)都接受std::locale作为最后一个参数,并使用默认构造的区域设置作为默认参数。

你是否经常使用不区分大小写的方法,并且需要更好的性能?只需创建一个持有std::locale::classic()std::locale变量,并将其传递给所有算法。在小弦上,大部分时间被std::locale结构吃掉,而不是被算法吃掉:

#include <boost/algorithm/string/erase.hpp>

void erasing_examples_locale() {
    namespace ba = boost::algorithm;

    const std::locale loc = std::locale::classic();

    const std::string r1
        = ba::ierase_all_copy(str, "hello", loc);

    const std::string r2
        = ba::ierase_nth_copy(str, ",", 1, loc);

    // ...
}

C++ 17 没有Boost.StringAlgorithm方法和类。但是,它有一个std::string_view类,可以在不分配内存的情况下使用子字符串。在本章接下来的两个食谱中,你可以找到更多关于std::string_view类的信息。

请参见

  • 官方文件包含了很多例子和所有方法在http://boost.org/libs/algorithm/string的完整参考
  • 有关Boost.StringAlgorithm库的更多信息,请参见本章中的改变大小写和不区分大小写的比较配方

用两个迭代器表示一个字符串

有些情况下,我们需要将一些字符串拆分为子字符串,并对这些子字符串做一些事情。在这个方法中,我们希望将字符串拆分成句子、计数字符和空格,当然,我们希望使用 Boost 并尽可能高效。

准备好

对于这个食谱,你需要一些标准库算法的基础知识。

怎么做...

借助 Boost,这很容易做到:

  1. 首先,包括正确的标题:
#include <iostream>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <algorithm>
  1. 现在,让我们定义我们的测试字符串:
int main() {
    const char str[] =
        "This is a long long character array."
        "Please split this character array to sentences!"
        "Do you know, that sentences are separated using period, "
        "exclamation mark and question mark? :-)"
    ;
  1. 我们为拆分迭代器制作了一个typedef:
    typedef boost::split_iterator<const char*> split_iter_t;
  1. 构造迭代器:
    split_iter_t sentences = boost::make_split_iterator(str,
        boost::algorithm::token_finder(boost::is_any_of("?!."))
    );
  1. 现在,我们可以在匹配项之间迭代:
    for (unsigned int i = 1; !sentences.eof(); ++ sentences, ++ i) {
        boost::iterator_range<const char*> range = *sentences;
        std::cout << "Sentence #" << i << " : \t" << range << '\n';
  1. 计算字符数:
        std::cout << range.size() << " characters.\n";
  1. 数一数空白:
        std::cout 
            << "Sentence has " 
            << std::count(range.begin(), range.end(), ' ') 
            << " whitespaces.\n\n";
    } // end of for(...) loop
} // end of main()

就这样。现在,如果我们运行一个示例,它将输出:

 Sentence #1 : This is a long long character array
 35 characters.
 Sentence has 6 whitespaces.

 Sentence #2 : Please split this character array to sentences
 46 characters.
 Sentence has 6 whitespaces.

 Sentence #3 : Do you know, that sentences are separated using dot,
 exclamation mark and question mark
 90 characters.
 Sentence has 13 whitespaces.

 Sentence #4 : :-)
 4 characters.
 Sentence has 1 whitespaces.

它是如何工作的...

这个食谱的主要思想是我们不需要从子串中构造std::string。我们甚至不需要一次性标记整个字符串。我们所需要做的就是找到第一个子串,并将其作为一对迭代器返回到子串的开头和结尾。如果我们需要更多的子串,找到下一个子串,并为该子串返回一对迭代器。

现在,让我们仔细看看boost::split_iterator。我们使用boost::make_split_iterator函数构建了一个,该函数将range作为第一个参数,将二进制查找器谓词(或二进制谓词)作为第二个参数。当split_iterator被取消引用时,它将第一个子字符串作为boost::iterator_range<const char*>返回,该子字符串只保存一对指针,并且有一些方法可以使用它们。当我们增加split_iterator时,它会尝试寻找下一个子串,如果没有找到子串,split_iterator::eof()会返回true

Default constructed split iterator represents an eof(). So we could rewrite the loop condition from !sentences.eof() to sentences != split_iter_t(). You could also use the split iterators with algorithms, for example: std::for_each(sentences, split_iter_t(), [](auto range){ /**/ });.

还有更多...

boost::iterator_range类广泛应用于所有的 Boost 库。在必须返回一对迭代器或者函数应该接受/使用一对迭代器的情况下,您可能会发现它甚至对您自己的代码也很有用。

boost::split_iterator<>boost::iterator_range<>类接受前向迭代器类型作为模板参数。因为我们正在处理前面例子中的字符数组,所以我们提供了const char*作为迭代器。如果我们使用std::wstring,我们将需要使用boost::split_iterator<std::wstring::const_iterator>boost::iterator_range<std::wstring::const_iterator>类型。

C++ 17 既没有iterator_range也没有split_iterator。然而,关于接受像“T2”这样的名字为“T3”的班级的讨论仍在继续。

boost::iterator_range类没有虚函数,没有动态内存分配,快速高效。然而,它的输出流操作符<<没有针对字符数组的特定优化,因此流可能会很慢。

boost::split_iterator类中有一个boost::function类,所以为大函子构建它可能会很慢。迭代只会增加很小的开销,即使在性能关键的部分你也感觉不到。

请参见

  • 下一个食谱将告诉你boost::iterator_range<const char*>的一个不错的替代品
  • Boost.StringAlgorithm的官方文档可能会在http://boost.org/libs/algorithm/string给你提供更多关于课程的详细信息和一大堆例子
  • 更多关于boost::iterator_range的信息可以在这里找到:http://boost.org/libs/range;这是Boost.Range图书馆的一部分,在本书中没有描述,但你可能希望自己研究一下

使用对字符串类型的引用

这个食谱是本章最重要的食谱!让我们来看一个非常常见的情况,我们编写一些函数来接受一个字符串,并在startsends参数中传递的字符值之间返回字符串的一部分:

#include <string>
#include <algorithm>

std::string between_str(const std::string& input, char starts, char ends) {
    std::string::const_iterator pos_beg 
        = std::find(input.begin(), input.end(), starts);
    if (pos_beg == input.end()) {
        return std::string();
    }
    ++ pos_beg;

    std::string::const_iterator pos_end 
        = std::find(pos_beg, input.end(), ends);

    return std::string(pos_beg, pos_end);
}

你喜欢这个实现吗?在我看来,这很糟糕。考虑以下对它的调用:

between_str("Getting expression (between brackets)", '(', ')'); 

在这个例子中,一个临时的std::string变量是由"Getting expression (between brackets)"构造的。字符数组足够长,所以很有可能在std::string构造函数内部调用动态内存分配,并将字符数组复制到其中。然后,在between_str函数内部的某个地方,新的std::string正在构建,这也可能导致另一个动态内存分配和复制。

所以,这个简单的函数可能,而且在大多数情况下会:

  • 调用动态内存分配(两次)
  • 复制字符串(两次)
  • 释放内存(两次)

我们能做得更好吗?

准备好

这个食谱需要标准库和 C++ 的基础知识。

怎么做...

我们在这里并不真正需要std::string类,我们只需要一些轻量级的类,它不管理资源,只有一个指向字符数组和数组大小的指针。Boost 为此提供了boost::string_view类。

  1. 要使用boost::string_view类,请包含以下标题:
#include <boost/utility/string_view.hpp>
  1. 更改方法的签名:
boost::string_view between(
    boost::string_view input,
    char starts,
    char ends)
  1. 将功能体内各处的std::string改为boost::string_view::
{
    boost::string_view::const_iterator pos_beg 
        = std::find(input.cbegin(), input.cend(), starts);
    if (pos_beg == input.cend()) {
        return boost::string_view();
    }
    ++ pos_beg;

    boost::string_view::const_iterator pos_end 
        = std::find(pos_beg, input.cend(), ends);
    // ...
  1. boost::string_view构造函数接受大小作为第二个参数,所以我们需要稍微修改一下代码:
    if (pos_end == input.cend()) {
        return boost::string_view(pos_beg, input.end() - pos_beg);
    }

    return boost::string_view(pos_beg, pos_end - pos_beg);
}

就这样!现在我们可以称之为between("Getting expression (between brackets)", '(', ')'),它将在没有任何动态内存分配和字符复制的情况下工作。我们仍然可以将其用于std::string:

   between(std::string("(expression)"), '(', ')')

它是如何工作的...

如前所述,boost::string_view只包含一个指向字符数组和数据大小的指针。它有很多构造函数,可以用不同的方式初始化:

    boost::string_view r0("^_^");

    std::string O_O("O__O");
    boost::string_view r1 = O_O;

    std::vector<char> chars_vec(10, '#');
    boost::string_view r2(&chars_vec.front(), chars_vec.size());

boost::string_view类具有container类所需的所有方法,因此它可用于标准库算法和 Boost 算法:

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/lexical_cast.hpp>
#include <iterator>
#include <iostream>

void string_view_algorithms_examples() {
    boost::string_view r("O_O");
    // Finding single symbol.
    std::find(r.cbegin(), r.cend(), '_');

    // Will print 'o_o'.
    boost::to_lower_copy(std::ostream_iterator<char>(std::cout), r);
    std::cout << '\n';

    // Will print 'O_O'.
    std::cout << r << '\n';

    // Will print '^_^'.
    boost::replace_all_copy(
        std::ostream_iterator<char>(std::cout), r, "O", "^"
    );
    std::cout << '\n';

    r = "100";
    assert(boost::lexical_cast<int>(r) == 100);
}

The boost::string_view class does not really own string, so all its methods return constant iterators. Because of that, we cannot use it in methods that modify data, such as boost::to_lower(r).

在使用boost::string_view时,我们必须额外关注它所引用的数据;它必须存在并在引用它的boost::string_view变量的整个生命周期内有效。

Before Boost 1.61 there was no boost::string_view class, but the boost::string_ref class was used instead. Those classes are really close. boost::string_view closer follows the C++ 17 design and has better constexpr support. Since Boost 1.61, boost::string_ref is deprecated.

string_view类快速高效,因为从不分配内存,没有虚函数!尽可能使用它们。它们被设计为const std::string&const char*参数的替代产品。这意味着您可以替换以下三个功能:

void foo(const std::string& s);
void foo(const char* s);
void foo(const char* s, std::size_t s_size);

只有一个:

void foo(boost::string_view s);

还有更多...

boost::string_view类是一个 C++ 17 类。如果你的编译器是 C++ 17 兼容的,你可以在std::名字空间的<string_view>头中找到它。

Boost's and standard library's version support constexpr usage of string_views; however, std::string_view currently has more functions marked with constexpr.

请注意,我们已经通过值而不是常数引用接受了string_view变量。这是通过boost::string_viewstd::string_view的推荐方式,因为:

  • string_view是一个小类,里面有琐碎的类型。通过值传递它通常会导致更好的性能,因为间接寻址更少,并且它允许编译器进行更多的优化。
  • 在其他情况下,当没有性能差异时,写string_view val比写const string_view& val短。

就像 C++ 17 的std::string_view一样,boost::string_view类实际上是一个typedef:

typedef basic_string_view<char, std::char_traits<char> > string_view; 

您可能还会发现以下类型定义对boost::std::名称空间中的宽字符很有用:

typedef basic_string_view<wchar_t,  std::char_traits<wchar_t> > wstring_view; 

typedef basic_string_view<char16_t, std::char_traits<char16_t> > u16string_view; 

typedef basic_string_view<char32_t, std::char_traits<char32_t> > u32string_view; 

请参见

string_refstring_view的增强文档可以在http://boost.org/libs/utility找到。