357

How can I find out if a string ends with another string in C++?

Community
  • 1
  • 1
sofr
  • 5,407
  • 6
  • 28
  • 29

22 Answers22

252

Simply compare the last n characters using std::string::compare:

#include <iostream>

bool hasEnding (std::string const &fullString, std::string const &ending) {
    if (fullString.length() >= ending.length()) {
        return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
    } else {
        return false;
    }
}

int main () {
    std::string test1 = "binary";
    std::string test2 = "unary";
    std::string test3 = "tertiary";
    std::string test4 = "ry";
    std::string ending = "nary";

    std::cout << hasEnding (test1, ending) << std::endl;
    std::cout << hasEnding (test2, ending) << std::endl;
    std::cout << hasEnding (test3, ending) << std::endl;
    std::cout << hasEnding (test4, ending) << std::endl;

    return 0;
}
Uli Köhler
  • 13,012
  • 16
  • 70
  • 120
kdt
  • 27,905
  • 33
  • 92
  • 139
  • Yeah, this is the best way to do it, without doubt. – Noldorin May 17 '09 at 08:39
  • I tried but somehow it didn't work I need my code also to handle if after the ending it has \r or \n etc. thanks for the help – sofr May 17 '09 at 14:14
  • 4
    I always hate calculating indices of substrings, it's very off-by-one prone... I'ld rather iterate backwards from the end of both strings, trying to find a mismatch. – xtofl May 18 '09 at 08:15
  • @sofr: Sounds like you should be stripping \r and \n from the end of your strings first (either just fullString or both it and ending). The following function does this in-place: void trimCrLf(string& s) { string::size_type n = s.size(); if (n && s[n - 1] == '\n') --n; if (n && s[n - 1] == '\r') --n; s.erase(n); } – j_random_hacker May 18 '09 at 09:30
  • Just wanted to point out for the the usecase of `ending = ""` this will return true. (Which is expected behavior IMO, and also what Java does). – LeviX Jul 11 '12 at 19:50
  • 36
    @Noldorin I don't agree. This is a no-brainer -- the best way to do it is to use a library. It's a shame the C++ Standard library does so few useful things. – masterxilo Aug 02 '14 at 19:05
  • 3
    @masterxilo What library do you propose to solve this problem and how is that library a better choice than a (basically) one-line function? – Brandin Sep 01 '14 at 10:44
  • 70
    @Brandin Because it's a such basic functionality. C++ forces us to reprogram again and again the same functionalities which are provided out-of-the-box in any other modern computer language. The fact that people need to go to stackoverflow to solve this question shows there is a pb. – Conchylicultor Feb 01 '17 at 00:33
  • @Conchylicultor Wrong. You could have looked at the compare function or browsed the std::string member functions in any C++ documentation source. For example, look at the second if statement in the example here: http://www.cplusplus.com/reference/string/string/compare/ It is precisely the solution used in this answer. – Brandin Feb 01 '17 at 09:22
  • 2
    C++ should really look at Python to make trivial things easier! It does difficult stuff easily but easy stuff difficult. – user997112 Sep 07 '21 at 16:16
  • 1
    @Brandin: I have *NO* idea what point you're trying to make. Javascript: String.prototype.endsWith(). Python: Python String endswith() Method, C#: String.EndsWith. Clojure: str/ends-with. Dart: endsWith. Go: hasSuffix (lolz). Haskel: isSuffixOf. Java: endsWith. Pascal: AnsiEndsStr (don't ask). Ruby: end_with. Rust: ends_with. Scala: endsWith. C++: An ugly patch of unreadableness which invariably generates 822 compiler errors. I've been programming professionally in C++ for as long as there was C++; and it's just gets worse without ever getting better. – Robin Davies Mar 05 '22 at 15:59
  • 1
    @RobinDavies Apparently the C++ committee listened to this -- As of C++20 it is in the language: https://en.cppreference.com/w/cpp/string/basic_string/ends_with On second look, the functionality is a bit counter intuitive. It is clearly there in compare, but not easy to use correctly for this case. The standard version is equivalent to `return size() >= x.size() && compare(size() - x.size(), npos, x) == 0` which is basically what is written in this answer, but the standard formulation is more compact and is a member function of the same class. – Brandin Mar 07 '22 at 11:03
  • 1
    Anyway, the fact that it was not provided for so long in C++ was clearly to me a design choice of conservatism. Don't include things that are not clearly needed. Yet, in the end, std::ends_with seems like a useful addition. I would personally prefer to use the standard version, if I'm programming in C++20. If I'm programming for an older compiler, though, my preference is still to make my own function like this if needed, and not to get a third party library like Boost just to have a few things like this. The original commentor never actually answered which library she would have preferred. – Brandin Mar 07 '22 at 11:06
215

Use this function:

inline bool ends_with(std::string const & value, std::string const & ending)
{
    if (ending.size() > value.size()) return false;
    return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
tshepang
  • 12,111
  • 21
  • 91
  • 136
Joseph
  • 2,153
  • 1
  • 12
  • 3
  • 3
    Beware that MSVC10 does not like this solution: `std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()` In debug mode, it throws: `_DEBUG_ERROR("string iterator not decrementable");` – remi.chateauneu May 28 '14 at 16:31
  • 3
    @remi.chateauneu I'm sure they fixed their big bug by now ;-) – Deduplicator Nov 26 '20 at 02:22
177

Use boost::algorithm::ends_with (see e.g. http://www.boost.org/doc/libs/1_34_0/doc/html/boost/algorithm/ends_with.html ):

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

// works with const char* 
assert(boost::algorithm::ends_with("mystring", "ing"));

// also works with std::string
std::string haystack("mystring");
std::string needle("ing");
assert(boost::algorithm::ends_with(haystack, needle));

std::string haystack2("ng");
assert(! boost::algorithm::ends_with(haystack2, needle));
Andre Holzner
  • 18,333
  • 6
  • 54
  • 63
164

Note, that starting from c++20 std::string will finally provide starts_with and ends_with. Seems like there is a chance that by c++30 strings in c++ might finally become usable, if you aren't reading this from distant future, you can use these startsWith/endsWith with C++17:

#if __cplusplus >= 201703L // C++17 and later 
#include <string_view>

static bool endsWith(std::string_view str, std::string_view suffix)
{
    return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}

static bool startsWith(std::string_view str, std::string_view prefix)
{
    return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
}
#endif // C++17

If you are stuck with older C++, you may use these:

#if __cplusplus < 201703L // pre C++17
#include <string>

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

static bool startsWith(const std::string& str, const std::string& prefix)
{
    return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
}

and some extra helper overloads:

static bool endsWith(const std::string& str, const char* suffix, unsigned suffixLen)
{
    return str.size() >= suffixLen && 0 == str.compare(str.size()-suffixLen, suffixLen, suffix, suffixLen);
}

static bool endsWith(const std::string& str, const char* suffix)
{
    return endsWith(str, suffix, std::string::traits_type::length(suffix));
}

static bool startsWith(const std::string& str, const char* prefix, unsigned prefixLen)
{
    return str.size() >= prefixLen && 0 == str.compare(0, prefixLen, prefix, prefixLen);
}

static bool startsWith(const std::string& str, const char* prefix)
{
    return startsWith(str, prefix, std::string::traits_type::length(prefix));
}
#endif

IMO, c++ strings are clearly dysfunctional, and weren't made to be used in real world code. But there is a hope that this will get better at least.

Olivia Stork
  • 4,660
  • 5
  • 27
  • 40
Pavel P
  • 15,789
  • 11
  • 79
  • 128
  • 2
    Since str.compare does not return a boolean, it's not so smart to test for "==0" by using the not ("!") operator, as that may be confusing to readers. Please use "... && str.compare(...) == 0" for clarity. – Thomas Tempelmann Jul 22 '17 at 15:16
  • @Pavel Is there a reason to not use std::string::find in your "startsWith" methods? – Maxime Oudot Jun 12 '19 at 12:31
  • 8
    @MaximeOudot Off course there is! Why would you want to search entire string if you need to know if it starts with something? In other words, you may end up searching 100mb long string to find the piece at the end and then ignoring that result because it's not at the beginning of the string. – Pavel P Jun 12 '19 at 16:03
  • 33
    Plus "1" for c++30 prediction. – Super-intelligent Shade Dec 12 '19 at 21:43
  • +1 though you should not blindly and unnecessarily write the equality comparison backwards. It's never been a good idea, and most particularly in situations where you'd naturally get a syntax error if accidentally missing an =. – Nick Jul 15 '20 at 14:40
  • 2
    If you accept C++17 `std::string_view`s instead, it is more versatile and you won't need those variants anymore for efficiency. – Deduplicator Nov 26 '20 at 02:25
  • @Deduplicator Added `string_view` based versions. IMO if code that wanted to use these functions already had `std::string` args, then it's better to use std::string versions directly – Pavel P Nov 26 '20 at 13:35
48

I know the question's for C++, but if anyone needs a good ol' fashioned C function to do this:


/*  returns 1 iff str ends with suffix  */
int str_ends_with(const char * str, const char * suffix) {

  /*  note - it would be better to abort or return an error code here; see the comments  */
  if( str == NULL || suffix == NULL )
    return 0;

  size_t str_len = strlen(str);
  size_t suffix_len = strlen(suffix);

  if(suffix_len > str_len)
    return 0;

  return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len );
}

Tom
  • 18,685
  • 15
  • 71
  • 81
  • 5
    If you expect a string but get a `NULL`, that's an error. Thus, I would `assert()` or crash, instead of silently plodding on with corrupt state. – Deduplicator Nov 26 '20 at 17:32
27

The std::mismatch method can serve this purpose when used to backwards iterate from the end of both strings:

const string sNoFruit = "ThisOneEndsOnNothingMuchFruitLike";
const string sOrange = "ThisOneEndsOnOrange";

const string sPattern = "Orange";

assert( mismatch( sPattern.rbegin(), sPattern.rend(), sNoFruit.rbegin() )
          .first != sPattern.rend() );

assert( mismatch( sPattern.rbegin(), sPattern.rend(), sOrange.rbegin() )
          .first == sPattern.rend() );
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 3
    +1. I'd never noticed std::mismatch() before -- I wonder what else is in that algorithms header file that I've never looked at... – j_random_hacker May 18 '09 at 09:20
  • 3
    I think that's worth a SO question on it's own: have you ever browsed through the stl functions available? – xtofl May 18 '09 at 09:27
  • 6
    Note that this has the same requirement as `std::equal`: you need to check in advance that the supposed suffix isn't longer than the string you're searching for it in. Neglecting to check that leads to undefined behavior. – Rob Kennedy Feb 07 '12 at 21:26
  • @RobKennedy That could be remedied by using the 4-pronged [`std::mismatch()`](//en.cppreference.com/w/cpp/algorithm/mismatch). – Deduplicator Nov 26 '20 at 02:28
18

In my opinion simplest, C++ solution is:

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.rfind(suffix) == std::abs(s.size()-suffix.size());
}

Warning: If the match fails, this will search the entire string backwards before giving up, and thus potentially waste a lot of cycles.

kritzikratzi
  • 19,662
  • 1
  • 29
  • 40
baziorek
  • 2,502
  • 2
  • 29
  • 43
  • 13
    This is rather slow since you'll search the entire string `s` instead of just testing the end of it! – Alexis Wilke Dec 07 '13 at 08:33
  • 2
    @nodakai, if I happen to have a 1Mb string, it's going to be a lot more than nanoseconds. – Alexis Wilke Oct 17 '16 at 19:27
  • I don't think so… it needs to do strlen in any case, and then starts looking from the end. – LtWorf Oct 07 '17 at 09:49
  • 2
    And if it *fails* to match, it will search the entire string backwards before giving up. It needs to be a forward search starting from the candidate index: `ssize_t maybe_index = s.size()-suffix.size(); return maybe_index > 0 && (s.find(suffix, maybe_index) == maybe_index);` – ncoghlan Feb 25 '20 at 13:25
  • 4
    @LtWorf `std::string::size()` is a constant-time operation; it doesn't need `strlen`. – Thomas Feb 29 '20 at 12:36
12

Let a be a string and b the string you look for. Use a.substr to get the last n characters of a and compare them to b (where n is the length of b)

Or use std::equal (include <algorithm>)

Ex:

bool EndsWith(const string& a, const string& b) {
    if (b.size() > a.size()) return false;
    return std::equal(a.begin() + a.size() - b.size(), a.end(), b.begin());
}
Dario
  • 48,658
  • 8
  • 97
  • 130
  • How can i return true also if it ends after my string with \r or \n or both??? thanks! – sofr May 17 '09 at 11:14
  • @Dario: Your solution using std::equal() is good, the one using substr() not so much -- unless you're using COW strings (and few people are I believe), substr() implies creating a second copy of part of the string, implying dynamic memory allocation is involved. This can may fail, and in any case means more memory is used than other solutions (and it's almost certainly slower than other solutions). – j_random_hacker May 18 '09 at 09:37
8

Let me extend Joseph's solution with the case insensitive version (online demo)

#include <string>
#include <cctype>

static bool EndsWithCaseInsensitive(const std::string& value, const std::string& ending) {
    if (ending.size() > value.size()) {
        return false;
    }
    return std::equal(ending.crbegin(), ending.crend(), value.crbegin(),
        [](const unsigned char a, const unsigned char b) {
            return std::tolower(a) == std::tolower(b);
        }
    );
}
PolarBear
  • 1,117
  • 15
  • 24
  • Unless you include `tolower()` using `` instead of `` (or use using), that might fail to compile. Also, plain `char` might be signed, making this UB. – Deduplicator Nov 26 '20 at 02:33
  • @Deduplicator, thank you for your remarks. I have fixed my answer. But I don't understand your point about UB, could you please elaborate? – PolarBear Nov 26 '20 at 08:25
  • Just see [some docs](//en.cppreference.com/w/cpp/string/byte/tolower). In short, you have to cast to `unsigned char` before passing it to `tolower()`. Easiest way is changing the lambdas argument types to `unsigned char`. – Deduplicator Nov 26 '20 at 12:57
  • @Deduplicator, insane, thank you for pointing it out. I have fixed it – PolarBear Nov 26 '20 at 13:26
8

Use std::equal algorithm from <algorithms> with reverse iteration:

std::string LogExt = ".log";
if (std::equal(LogExt.rbegin(), LogExt.rend(), filename.rbegin())) {
   …
}

Starting from C++20 ends_with introduced.

Sergei Krivonos
  • 4,217
  • 3
  • 39
  • 54
  • 2
    While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained. – borchvm May 08 '20 at 09:19
  • @borchvm, added some explaination, hope it helps to comprehend – Sergei Krivonos May 08 '20 at 14:04
  • What if LogExt.length() is greater than filename.length()? This will read past the beginning of filename, possibly segfaulting. Seems like a bug. – Some Guy Oct 30 '20 at 03:16
  • 1
    It is ok if LogExt.length() is greater than filename.length(), std::equal returns false this way https://en.cppreference.com/w/cpp/algorithm/equal – Sergei Krivonos Oct 31 '20 at 16:49
4

you can use string::rfind

The full Example based on comments:

bool EndsWith(string &str, string& key)
{
size_t keylen = key.length();
size_t strlen = str.length();

if(keylen =< strlen)
    return string::npos != str.rfind(key,strlen - keylen, keylen);
else return false;
}
Ahmed
  • 7,148
  • 12
  • 57
  • 96
  • 3
    -1. Yes you could use it, but it's unnecessarily slow in the event that the string does not end with the supplied ending -- scanning will continue all the way back to the start of the string. Also, you don't mention that you need a subsequent test to make sure that the ending matches **at the end of the string**, rather than elsewhere in the string. – j_random_hacker May 17 '09 at 09:56
  • I just put the link of the needed function and I think it is very easy to do it from the documentation str.rfind(key,str.length()-key.length(),key.length()); – Ahmed May 17 '09 at 10:13
  • OK, that's efficient -- but in that case string::find() would work just as well. Also you need to mention the case where key.length() > str.length() -- the code you suggest in your comment will crash in this case. If you update your answer with this info I'll drop my -1. – j_random_hacker May 17 '09 at 16:04
3

the very same as above, here is my solution

 template<typename TString>
  inline bool starts_with(const TString& str, const TString& start) {
    if (start.size() > str.size()) return false;
    return str.compare(0, start.size(), start) == 0;
  }
  template<typename TString>
  inline bool ends_with(const TString& str, const TString& end) {
    if (end.size() > str.size()) return false;
    return std::equal(end.rbegin(), end.rend(), str.rbegin());
  }
dodjango
  • 65
  • 6
3

Check if str has suffix, using below:

/*
Check string is end with extension/suffix
*/
int strEndWith(char* str, const char* suffix)
{
  size_t strLen = strlen(str);
  size_t suffixLen = strlen(suffix);
  if (suffixLen <= strLen) {
    return strncmp(str + strLen - suffixLen, suffix, suffixLen) == 0;
  }
  return 0;
}
James Yang
  • 1,306
  • 4
  • 15
  • 25
2

I thought it makes sense to post a raw solution that doesn't use any library functions...

// Checks whether `str' ends with `suffix'
bool endsWith(const std::string& str, const std::string& suffix) {
    if (&suffix == &str) return true; // str and suffix are the same string
    if (suffix.length() > str.length()) return false;
    size_t delta = str.length() - suffix.length();
    for (size_t i = 0; i < suffix.length(); ++i) {
        if (suffix[i] != str[delta + i]) return false;
    }
    return true;
}

Adding a simple std::tolower we can make this case insensitive

// Checks whether `str' ends with `suffix' ignoring case
bool endsWithIgnoreCase(const std::string& str, const std::string& suffix) {
    if (&suffix == &str) return true; // str and suffix are the same string
    if (suffix.length() > str.length()) return false;
    size_t delta = str.length() - suffix.length();
    for (size_t i = 0; i < suffix.length(); ++i) {
        if (std::tolower(suffix[i]) != std::tolower(str[delta + i])) return false;
    }
    return true;
}
cute_ptr
  • 1,103
  • 9
  • 12
2
bool EndsWith(const std::string& data, const std::string& suffix)
{
    return data.find(suffix, data.size() - suffix.size()) != std::string::npos;
}

Tests

#include <iostream>
int main()
{
   cout << EndsWith(u8"o!hello!1", u8"o!") << endl; 
   cout << EndsWith(u8"o!hello!", u8"o!") << endl; 
   cout << EndsWith(u8"hello!", u8"o!") << endl; 
   cout << EndsWith(u8"o!hello!o!", u8"o!") << endl; 
   return 0;
}

Output

0 
1 
1 
1 
kritzikratzi
  • 19,662
  • 1
  • 29
  • 40
Joma
  • 3,520
  • 1
  • 29
  • 32
1

Regarding Grzegorz Bazior response. I used this implementation, but original one has bug (returns true if I compare ".." with ".so"). I propose modified function:

bool endsWith(const string& s, const string& suffix)
{
    return s.size() >= suffix.size() && s.rfind(suffix) == (s.size()-suffix.size());
}
Andrew123
  • 437
  • 1
  • 4
  • 5
1

another option is to use regex. The following code makes the search insensitive to upper/lower case:

bool endsWithIgnoreCase(const std::string& str, const std::string& suffix) {
  return std::regex_search(str,
     std::regex(std::string(suffix) + "$", std::regex_constants::icase));
}

probably not so efficient, but easy to implement.

Julien Pilet
  • 504
  • 4
  • 5
1

Found this nice answer to the similar "startWith"-problem:

How do I check if a C++ std::string starts with a certain string, and convert a substring to an int?

You can adopt the solution to only search at the last place in the string:

bool endsWith(const std::string& stack, const std::string& needle) {
    return stack.find(needle, stack.size() - needle.size()) != std::string::npos;
}

This way you can make it short, fast, use standard c++ and make it readable.

Lasersköld
  • 2,028
  • 14
  • 20
1

If, like me, you need endsWith to check a file extension, you can use the std::filesystem library:

std::filesystem::path("/foo/bar.txt").extension() == ".txt"
Dan
  • 12,409
  • 3
  • 50
  • 87
0

If you're like me and no so into C++ purism, here's an old skool hybrid. There's some advantage when strings are more than a handful of characters, as most memcmp implementations compare machine words when possible.

You need to be in control of the character set. For example, if this approach is used with utf-8 or wchar type, there's some disadvantage as it won't support character mapping - e.g., when two or more characters are logically identical.

bool starts_with(std::string const & value, std::string const & prefix)
{
    size_t valueSize = value.size();
    size_t prefixSize = prefix.size();

    if (prefixSize > valueSize)
    {
        return false;
    }

    return memcmp(value.data(), prefix.data(), prefixSize) == 0;
}


bool ends_with(std::string const & value, std::string const & suffix)
{
    size_t valueSize = value.size();
    size_t suffixSize = suffix.size();

    if (suffixSize > valueSize)
    {
        return false;
    }

    const char * valuePtr = value.data() + valueSize - suffixSize;

    return memcmp(valuePtr, suffix.data(), suffixSize) == 0;
}
jws
  • 2,171
  • 19
  • 30
0

My two cents:

bool endsWith(std::string str, std::string suffix)
{
   return str.find(suffix, str.size() - suffix.size()) != string::npos;
}
drop table
  • 25
  • 3
0
bool endswith(const std::string &str, const std::string &suffix)
{
    string::size_type totalSize = str.size();
    string::size_type suffixSize = suffix.size();

    if(totalSize < suffixSize) {
        return false;
    }

    return str.compare(totalSize - suffixSize, suffixSize, suffix) == 0;
}