这个实现应该很快:
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
n = std::min(n, s.size());
std::string ret;
ret.reserve(2*n);
ret.append(s.begin(), s.begin() + n);
ret.append(s.end() - n, s.end());
return ret;
}
它通过了所有三个单元测试。
使用 GNU libstdc++ 时,声明和初始化的行ret
非常快,因为 libstdc++ 使用全局“空字符串”变量。因此,它只是一个指针副本。调用begin
and end
ons
也很快,因为它们会解析为begin
and 和and的 const 版本end
,因此内部表示不会“泄露”。使用 libstdc++,is是一个指针类型和随机访问迭代器。因此,当调用获取输入范围的长度时,就是指针差分操作。此外,结果类似于. 最后,该操作确保有足够的内存可用于返回值。begin() const
end() const
s
std::string::const_iterator
const char*
std::string::append<const char*>(const char*, const char*)
std::distance
std::string::append<const char*>(const char*, const char*)
memmove
reserve
编辑:
出于好奇,这里是ret
MinGW g++ 4.5.0 的程序集输出中的初始化:
movl $__ZNSs4_Rep20_S_empty_rep_storageE+12, (%ebx)
它只是将指针复制到全局“空表示”。
EDIT2:
好的。我现在已经使用 g++ 4.5.0 和 Visual C++ 16.00.30319.01 测试了四个变体:
变体 1(“c_str 变体”):
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
std::string::size_type s_size = s.size();
n = std::min(n, s_size);
std::string ret;
ret.reserve(2*n);
const char *s_cStr = s.c_str(), *s_cStr_end = s_cStr + s_size;
ret.append(s_cStr, s_cStr + n);
ret.append(s_cStr_end - n, s_cStr_end);
return ret;
}
变体 2(“数据字符串”变体):
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
std::string::size_type s_size = s.size();
n = std::min(n, s_size);
std::string ret;
ret.reserve(2*n);
const char *s_data = s.data(), *s_data_end = s_data + s_size;
ret.append(s_data, s_data + n);
ret.append(s_data_end - n, s_data_end);
return ret;
}
变体 3:
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
std::string::size_type s_size = s.size();
n = std::min(n, s_size);
std::string ret(s);
std::string::size_type d = s_size - n;
return ret.replace(n, d, s, d, n);
}
变体 4(我的原始代码):
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
n = std::min(n, s.size());
std::string ret;
ret.reserve(2*n);
ret.append(s.begin(), s.begin() + n);
ret.append(s.end() - n, s.end());
return ret;
}
g++ 4.5.0 的结果是:
- 变体 4 是最快的
- 变体 3 次之(比变体 4 慢 5%)
- 变体 1 排在第三位(比变体 3 慢 2%)
- 变体 2 是第四个(比变体 1 慢 0.2%)
VC++ 16.00.30319.01 的结果是:
- 变体 1 是最快的
- 变体 2 次之(比变体 1 慢 3%)
- 变体 4 排在第三位(比变体 2 慢 4%)
- 变体 3 是第四个(比变体 4 慢 17%)
不出所料,最快的变体取决于编译器。但是,不知道会使用哪个编译器,我认为我的变体是最好的,因为它是 C++ 的熟悉风格,使用 g++ 时速度最快,使用 VC++ 时也不比变体 1 或 2 慢多少。
VC++ 结果中有趣的一件事是使用c_str
而不是data
更快。也许这就是为什么你的面试官说有比你的实施更快的方法。
编辑3:
实际上,我只是想到了另一种变体:
变体 5:
inline std::string first_last_n(std::string::size_type n, const std::string& s)
{
n = std::min(n, s.size());
std::string ret;
ret.reserve(2*n);
std::string::const_iterator s_begin = s.begin(), s_end = s.end();
ret.append(s_begin, s_begin + n);
ret.append(s_end - n, s_end);
return ret;
}
它就像变体 4 一样,只是s
保存了 for 的开始和结束迭代器。
在测试变体 5 时,它实际上在使用 VC++ 时击败了变体 2(数据字符串变体):
- 变体 1 是最快的
- 变体 5 次之(比变体 1 慢 1.6%)
- 变体 2 排在第三位(比变体 5 慢 1.4%)
- 变体 4 排在第三位(比变体 2 慢 4%)
- 变体 3 是第四个(比变体 4 慢 17%)