int index_of(string s, string t)
{
int index = 0;
if (s[index] == NULL)
句号。这不是 C++ 字符串的工作方式,如果你想使用它们,你必须解决这个问题。即使是 C 风格的字符串,也不要使用 NULL 来表示 ASCII 空字符。它们共享一个名称但用途不同,您不应使用 NULL 来表示整数零(字符是整数类型,空字符是它们的零值)。使用'\0' 或仅使用if (s[index])。
但是,除非您知道索引有效,否则不允许您索引 std::string。为此,请将索引与 s.size() 进行比较(并确保它大于或等于 0)。即便如此,您在这里真正测试的是 s 是否为空,并且它有一个特殊的方法可以做到这一点:
if (s.empty())
继续:
else if (starts_with(s, t, ++index))
递增和递减内部表达式,尤其是在这里,可能会让初学者感到困惑而没有任何优势。它们的主要优点是代码简洁明了,但您必须先了解代码的主要部分,即使这样,有经验的程序员有时也会受益于稍微冗长一点。
有趣的是,同样参与早期 C 历史的 Go 的创造者甚至将增量从表达式变成了 语句,我相信清晰性是很大一部分原因。
从头开始
你想用这个签名实现一个函数:
int index_of(string haystack, string needle);
// returns the index of needle in haystack, if found
// otherwise returns -1
我故意将那些带有签名的 cmets 包括在内:它们是此函数的公共接口的一部分。更好的参数名称也会增加清晰度。
确定您需要考虑的情况:
- 针是空的(您可以通过多种方式处理)
- 干草堆是空的:返回-1
- 此时我们知道 haystack 和 needle 都不是空的
- 剩下的两种情况是算法的关键:
- haystack 的第一个字符与 needle 的第一个字符不匹配
- 第一个字符匹配
当第一个字符匹配时,你有两个子情况:
- needle 中没有更多字符:找到匹配项
- 还有更多字符:继续检查
我把这些写成一个递归算法,它接收每个字符串(和子字符串)的“新副本”,而不是使用索引。但是,您可以通过将“第一个字符”更改为“当前字符”来转换为使用索引,对于“空”条件也是如此。在这种情况下,您将需要使用 两个 索引(到目前为止,尝试只使用一个可能是您的主要绊脚石),除非您有一个比较子字符串的帮助功能(尽管我我不确定您的教授是否有单独的意图发表此评论)。
将上述散文直接翻译成代码:
int index_of(string haystack, string needle) {
if (needle.empty()) return 0;
// this implementation considers empty substrings to occur at the start of any
// string, even an empty haystack; you could also make it an error to call
// index_of when needle is empty, or just return -1
if (haystack.empty()) return -1;
assert(!needle.empty() && !haystack.empty()); // I wouldn't normally include
// this, since we just checked these conditions, but this is the "at this
// point we know both haystack and needle are not empty" that I mentioned
if (haystack[0] != needle[0]) {
// mark A, see below
int index = index_of(haystack.substr(1), needle);
return index != -1 ? index + 1 : index;
}
if (needle.length() == 1) return 0; // found complete match
// note the way I chose to handle needle.empty() above makes this unnecessary
// mark B, see below
// partial match (of the first character), continue matching
int index = index_of(haystack.substr(1), needle.substr(1)); // strip first
return index == 0 ? 0 : -1;
// must check index == 0 exactly, if -1 then we must return that, and if not 0
// then we've found a "broken" needle, which isn't a real match
}
断针注释暗示了该代码的效率低下,因为它将递归调用分为两类:必须在标记 B 处匹配 1(分割成子字符串后为 0),并且可以在标记处匹配任何位置答:我们可以使用辅助函数来改进这一点,为此我将使用 std::string 的 operator== 重载(对 haystack 的子字符串进行操作)。这产生了经典“naive strstr”的递归等价物:
int index_of(string haystack, string needle) {
if (needle.empty()) return 0;
if (haystack.empty()) return -1;
if (haystack.substr(0, needle.length()) == needle()) {
return 0;
}
int index = index_of(haystack.substr(1), needle);
if (index != -1) index++;
return index;
}
当使用带有 string::compare 作为帮助器的 haystack 索引时,不需要针索引:
// might not be exposed publicly, but could be
int index_of(string const& haystack, int haystack_pos, string const& needle) {
// would normally use string const& for all the string parameters in this
// answer, but I've mostly stuck to the prototype you already have
// shorter local name, keep parameter name the same for interface clarity
int& h = haystack_pos;
// preconditions:
assert(0 <= h && h <= haystack.length());
if (needle.empty()) return h;
if (h == haystack.length()) return -1;
if (haystack.compare(h, needle.length(), needle) == 0) {
return h;
}
return index_of(haystack, h+1, needle);
}
int index_of(string haystack, string needle) {
// sets up initial values or the "context" for the common case
return index_of(haystack, 0, needle);
}
注意这个版本是尾递归的,但这仍然是一个简单的算法和更高级的算法exist。
如果我有更多时间,我会写一封更短的信。
——西塞罗
您已经说过这对您有很大帮助,但是,即使我刚刚包含了其他示例,我似乎也缺乏它。在我看来,子字符串搜索不是一个好的递归练习,这可能就是原因。