How can I find out if a string ends with another string in C++?
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;
}
ending = ""
this will return true. (Which is expected behavior IMO, and also what Java does). –
Seraphic 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. –
Feltonfelts 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());
}
std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()
In debug mode, it throws: _DEBUG_ERROR("string iterator not decrementable");
–
Lauree 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));
std::ranges::ends_with()
instead. –
Cranny Note, that starting from C++20, std::string
provides starts_with
and ends_with
. Seems like there is a chance that by C++30 strings in C++ might finally become usable.
If you are stuck with an ancient environment, you can use these starts_with
/ends_with
with C++17:
#if __cplusplus >= 201703L // C++17 and later
#include <string_view>
static bool ends_with(std::string_view str, std::string_view suffix)
{
return str.size() >= suffix.size() && str.compare(str.size()-suffix.size(), suffix.size(), suffix) == 0;
}
static bool starts_with(std::string_view str, std::string_view prefix)
{
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
}
#endif // C++17
If your environment is nearer prehistoric, you may use these:
#if __cplusplus < 201703L // pre C++17
#include <string>
static bool ends_with(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() && str.compare(str.size()-suffix.size(), suffix.size(), suffix) == 0;
}
static bool starts_with(const std::string& str, const std::string& prefix)
{
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
}
// some extra helper overloads:
static bool ends_with(const std::string& str, const char* suffix, unsigned suffixLen)
{
return str.size() >= suffixLen && str.compare(str.size()-suffixLen, suffixLen, suffix, suffixLen) == 0;
}
static bool ends_with(const std::string& str, const char* suffix)
{
return ends_with(str, suffix, std::string::traits_type::length(suffix));
}
static bool starts_with(const std::string& str, const char* prefix, unsigned prefixLen)
{
return str.size() >= prefixLen && str.compare(0, prefixLen, prefix, prefixLen) == 0;
}
static bool starts_with(const std::string& str, const char* prefix)
{
return starts_with(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.
std::string_view
s instead, it is more versatile and you won't need those variants anymore for efficiency. –
Ragged 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 –
Scat std::string_view
from a std::string
is likely zero overhead, so no benefit to the std::string
version at all. –
Cranny std::string
has far too many member functions that merely duplicate what standard range algorithms provide (e.g. ends_with()
member that just returns the same as std::ranges::ends_with(*this, other)
). –
Cranny std::string
refs directly is similar to raw pointers with no code generated. Converting to std::string_view
is, as you say, likely, but it is not zero overhead. Depending on the use, that "likely" part may be the reason not to use std::strings at all. Also using std::string_view conversion generates code at call sites, this has impact on binary size for no reason. –
Scat std::string
, then I could see why you might think that. But the string interface is much more than that, since it's a standard Range, and so fits perfectly with <algorithm>
. –
Cranny 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 );
}
NULL
, that's an error. Thus, I would assert()
or crash, instead of silently plodding on with corrupt state. –
Ragged 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() );
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. –
Heptode std::mismatch()
. –
Ragged In my opinion simplest, C++ solution is:
bool endsWith(const std::string& s, const std::string& suffix)
{
return s.rfind(suffix) == (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.
s
instead of just testing the end of it! –
Cannae ssize_t maybe_index = s.size()-suffix.size(); return maybe_index > 0 && (s.find(suffix, maybe_index) == maybe_index);
–
Maltose std::string::size()
is a constant-time operation; it doesn't need strlen
. –
Adnopoz 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());
}
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.
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);
}
);
}
tolower()
using <ctype.h>
instead of <cctype>
(or use using), that might fail to compile. Also, plain char
might be signed, making this UB. –
Ragged unsigned char
before passing it to tolower()
. Easiest way is changing the lambdas argument types to unsigned char
. –
Ragged 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;
}
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());
}
starts_with
does use 'string::compare'? Why not std::equal(start.begin(), start.end(), str.begin())
? –
Garment 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;
}
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"
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;
}
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());
}
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.
suffix
contains special characters? unfortunately this isn't reliable –
Rotherham 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.
#include <cstdlib>
#include <string>
bool starts_with(std::string const & value, std::string const & prefix)
{
std::size_t valueSize = value.size();
std::size_t prefixSize = prefix.size();
if (prefixSize > valueSize)
{
return false;
}
return std::memcmp(value.data(), prefix.data(), prefixSize) == 0;
}
bool ends_with(std::string const & value, std::string const & suffix)
{
std::size_t valueSize = value.size();
std::size_t suffixSize = suffix.size();
if (suffixSize > valueSize)
{
return false;
}
const char * valuePtr = value.data() + valueSize - suffixSize;
return std::memcmp(valuePtr, suffix.data(), suffixSize) == 0;
}
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) {
if (needle.empty()) {
return false;
}
return stack.find(needle, stack.size() - needle.size()) != std::string::npos;
}
This way you can make it short, fast (it does not search the whole string like rfind does in this example), use standard c++ and make it readable.
Edit: Updated with empty-check.
needle.size() > stack.size()
. You only get away with it because the overflowing subtraction results in invalid pos
argument to find()
for reasonable-length strings. And I recommend accepting std::string_view
arguments rather than requiring std::string
to be constructed. –
Cranny stack.size() - needle.size()
is always greater than stack.size()
in that case, but it's certainly worth an explanatory comment. Otherwise it looks like a bug to the next coder editing it! –
Cranny needle
that is larger than at least half of the memory's total ram, because size_t is large it can index all elements of any theoretical array on your system. –
Marmalade std::size_t
that's smaller than their pointer types, I've heard). So the needle only needs to be larger than half the maximum object size - but the subtraction should still be safe. –
Cranny 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;
}
Based on Lasersköld's answer.
Code - Test on Godbolt.org
#include <iostream>
#include <string>
bool EndsWith(const std::string& data, const std::string& suffix)
{
if(data.empty() or suffix.empty() or (data.length() < suffix.length()))
{
return false;
}
if(data == suffix)
{
return true;
}
return data.find(suffix, data.size() - suffix.size()) != std::string::npos;
}
int main()
{
std::cout << std::boolalpha;
std::cout << "Test 1 = " <<EndsWith("hello!😎", "😎") << std::endl;
std::cout << "Test 2 = " << EndsWith("😎hello!", "😎") << std::endl;
std::cout << "Test 3 = " << EndsWith("hello!😎 ", "😎") << std::endl;
std::cout << "Test 4 = " << EndsWith("hello!😎 ", " ") << std::endl;
std::cout << "Test 5 = " << EndsWith("", "😎") << std::endl;
std::cout << "Test 6 = " << EndsWith("hello!😎", "") << std::endl;
std::cout << "Test 7 = " << EndsWith("", "") << std::endl;
std::cout << "Test 8 = " << EndsWith("hello!😎", "hello!😎😎") << std::endl;
std::cout << "Test 9 = " << EndsWith("hello!😎", "hello!😎") << std::endl;
return 0;
}
Output
Test 1 = true
Test 2 = false
Test 3 = false
Test 4 = true
Test 5 = false
Test 6 = false
Test 7 = false
Test 8 = false
Test 9 = true
My two cents:
bool endsWith(std::string str, std::string suffix)
{
return str.find(suffix, str.size() - suffix.size()) != string::npos;
}
© 2022 - 2024 — McMap. All rights reserved.