如何检查字符串是否以 .txt 结尾

27

我正在学习基础C++,现在我从用户那里获得了一个字符串,并且我想检查他们是否输入了整个文件名(包括.txt)。我有这个字符串,但我该如何检查它是否以“.txt”结尾?

string fileName;

cout << "Enter filename: \n";
cin >> fileName;

string txt = fileName.Right(4);

Right(int)方法只适用于CString,因此上面的代码不起作用。我希望如果可能的话,能够使用普通字符串。有什么想法吗?


1
我猜你的问题更多地涉及到文件名而不是字符串,所以我建议你寻找能够以可移植的方式提取文件名扩展名的解决方案。许多库都提供了这方面的支持。 - Frédéric Hamidi
1
可能是重复的问题:在C++中查找字符串是否以另一个字符串结尾 - Victor Sergienko
12个回答

45

很遗憾,这个有用的函数不在标准库中。但它很容易编写。

bool has_suffix(const std::string &str, const std::string &suffix)
{
    return str.size() >= suffix.size() &&
           str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}

谢谢。但是我现在想知道为什么你把传递的参数变成了常量?bool has_suffix(std::string str, std::string suffix){}也可以工作。 - Chronicle
5
@编年史:这是一个关于“为什么使用const”的一般问题。 "const" 限定符是一种承诺,由编译器强制执行,即函数中的代码不会修改字符串。由于函数不会修改字符串,因此您可以做出这个承诺--然后函数就可以传递 const 字符串,而无需先复制它们。如果您省略 const,则可能需要在将其传递给函数之前复制字符串。 - Dietrich Epp
1
@Chronicle: 快速搜索可以找到很好的资料,其中包括Lightness Races in Orbit在这里的回答:https://dev59.com/FGMl5IYBdhLWcg3wwZIq - Dietrich Epp
@Chronicle 当你不改变参数时(只读访问),请始终使用 const&。即使你不明白为什么...也要这样做。然而,了解原因仍然是好的。 - CodeAngry

14

使用boost ends_with谓词:

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

if (boost::ends_with(fileName, ".txt")) { /* ... */ }

这是我的最爱。可以使用 boost::iends_with(...) 进行不区分大小写的比较。 - Ruslan Yushchenko

12

你已经得到了很多答案,但我决定再添加一个:

bool ends_with(std::string const &a, std::string const &b) {
    auto len = b.length();
    auto pos = a.length() - len;
    if (pos < 0)
        return false;
    auto pos_a = &a[pos];
    auto pos_b = &b[0];
    while (*pos_a)
        if (*pos_a++ != *pos_b++)
            return false;
    return true;
}

由于您已经得到了相当多的答案,也许进行一次快速测试和结果总结会很有价值:

#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <iomanip>

bool ends_with(std::string const &a, std::string const &b) {
    auto len = b.length();
    auto pos = a.length() - len;
    if (pos < 0)
        return false;
    auto pos_a = &a[pos];
    auto pos_b = &b[0];
    while (*pos_a)
        if (*pos_a++ != *pos_b++)
            return false;
    return true;
}

bool ends_with_string(std::string const& str, std::string const& what) {
    return what.size() <= str.size()
        && str.find(what, str.size() - what.size()) != str.npos;
}

bool has_suffix(const std::string &str, const std::string &suffix)
{
    return str.size() >= suffix.size() &&
        str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}

bool has_suffix2(const std::string &str, const std::string &suffix)
{
    bool index = str.find(suffix, str.size() - suffix.size());
    return (index != -1);
}

bool isEndsWith(const std::string& pstr, const std::string& substr)
{
    int tlen = pstr.length();
    int slen = substr.length();

    if (slen > tlen)
        return false;

    const char* tdta = pstr.c_str();
    const char* sdta = substr.c_str();

    while (slen)
    {
        if (tdta[tlen] != sdta[slen])
            return false;

        --slen; --tlen;
    }
    return true;
}

bool ends_with_6502(const std::string& str, const std::string& end) {
    size_t slen = str.size(), elen = end.size();
    if (slen <= elen) return false;
    while (elen) {
        if (str[--slen] != end[--elen]) return false;
    }
    return true;
}

bool ends_with_rajenpandit(std::string const &file, std::string const &suffix) {
    int pos = file.find(suffix);
    return (pos != std::string::npos);
}

template <class F>
bool test(std::string const &label, F f) {
    static const std::vector<std::pair<std::string, bool>> tests{
        { "this is some text", false },
        { "name.txt.other", false },
        { "name.txt", true }
    };
    bool result = true;

    std::cout << "Testing: " << std::left << std::setw(20) << label;
    for (auto const &s : tests)
        result &= (f(s.first, ".txt") == s.second);
    if (!result) {
        std::cout << "Failed\n";
        return false;
    }
    clock_t start = clock();
    for (int i = 0; i < 10000000; i++)
        for (auto const &s : tests)
            result &= (f(s.first, ".txt") == s.second);
    clock_t stop = clock();
    std::cout << double(stop - start) / CLOCKS_PER_SEC << " Seconds\n";
    return result;
}

int main() {
    test("Jerry Coffin", ends_with);
    test("Dietrich Epp", has_suffix);
    test("Dietmar", ends_with_string);
    test("Roman", isEndsWith);
    test("6502", ends_with_6502);
    test("rajenpandit", ends_with_rajenpandit);
}

使用gcc的结果:

Testing: Jerry Coffin           3.416 Seconds
Testing: Dietrich Epp           3.461 Seconds
Testing: Dietmar                3.695 Seconds
Testing: Roman                  3.333 Seconds
Testing: 6502                   3.304 Seconds
Testing: rajenpandit            Failed

使用VC++的结果:

Testing: Jerry Coffin           0.718 Seconds
Testing: Dietrich Epp           0.982 Seconds
Testing: Dietmar                1.087 Seconds
Testing: Roman                  0.883 Seconds
Testing: 6502                   0.927 Seconds
Testing: rajenpandit            Failed

是的,这些都是在相同的硬件上运行的,并且我运行了很多次,并尝试使用g ++的不同优化选项来看看是否可以使其至少接近于匹配VC++。但我做不到。我目前无法解释为什么g++会为此测试生成如此糟糕的代码,但我相当有信心它确实如此。


除了“do me, do me, do me... :P”这句玩笑话外,据我上次在互联网上看到的内容,g++的字符串实现很糟糕,没有SSO。参见http://herbsutter.com/2013/05/13/gotw-2-solution-temporary-objects/。 - NoSenseEtAl
1
@NoSenseEtAl:这些结果似乎确实支持了这一点。 - Jerry Coffin
1
'pos < 0'永远为假。length()返回无符号类型。只需明确比较std-string的长度,因为它们应该是O(1)的。https://dev59.com/0HVC5IYBdhLWcg3wjx1d#256309 - kornman00

4
使用 std::string::substr 函数。
if (filename.substr(std::max(4, filename.size())-4) == std::string(".txt")) {
    // Your code here
}

1
如果 filename.size() < 4,这可能会导致问题。 - Dietrich Epp
1
如果 filename.size() < 4,这仍然会引起问题。std::max() 函数在这里不会像你期望的那样工作。 - Dietrich Epp
1
提示:std::max(0, filename.size() - 4) == filename.size() - 4 始终成立。 - Dietrich Epp
1
这仍然会导致问题。你如何确保(long)filename.size()不会溢出? - syam
Sutter 也同意这是一个错误...据我所知,谷歌的编码标准不鼓励使用无符号整数...除非有有效的理由,例如表示位模式而不是数字,或者需要定义溢出模2^N,否则不应使用无符号整数类型,如 uint32_t。特别是不要使用无符号类型来表示一个数字永远不会是负数。相反,应该使用断言来实现这一点。 - NoSenseEtAl
显示剩余15条评论

3
bool has_suffix(const std::string &str, const std::string &suffix)
{
    std::size_t index = str.find(suffix, str.size() - suffix.size());
    return (index != std::string::npos);
}

3
你可以使用另一个字符串来验证扩展名,像这样:
string fileName;

cout << "Enter filename: \n";
cin >> fileName;

//string txt = fileName.Right(4);
string ext="";
for(int i = fileName.length()-1;i>fileName.length()-5;i--)
{
    ext += fileName[i];
}
cout<<ext;
if(ext != "txt.")
    cout<<"error\n";

检查是否等于"txt.",因为i以文件名的长度开始,所以ext的填充方式相反。


2

最简单的方法可能是验证字符串是否足够长来容纳".txt",并检查该字符串是否可以在位置size() - 4被找到,例如:

bool ends_with_string(std::string const& str, std::string const& what) {
    return what.size() <= str.size()
        && str.find(what, str.size() - what.size()) != str.npos;
}

我不确定这是否是在推广C++的书中展示的例子。当然,这不是你的错... - 6502

2

不幸的是,标准库中缺少这个功能,并且编写起来有些烦人。这是我的尝试:

bool ends_with(const std::string& str, const std::string& end) {
    size_t slen = str.size(), elen = end.size();
    if (slen < elen) return false;
    while (elen) {
        if (str[--slen] != end[--elen]) return false;
    }
    return true;
}

2

除了上述提到的选项,我能想到另外两个选项:
1)正则表达式 - 对于这个问题来说可能有点过度,但是简单的正则表达式在我看来很好读懂。
2)rbegin - 这个选项还不错,也许我漏掉了什么,以下是示例代码:

bool ends_with(const string& s, const string& ending)
{
return (s.size()>=ending.size()) && equal(ending.rbegin(), ending.rend(), s.rbegin());
}

http://coliru.stacked-crooked.com/a/4de3eafed3bff6e3


到目前为止最干净的解决方案 - Slava

1
自C++17起,您可以利用path类来自文件系统库
#include <filesystem>

bool ends_with_txt(const std::string& fileName) {
    return std::filesystem::path{fileName}.extension() == ".txt";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接