Step 3 of 5
const references
Passing a std::string, a vector, or any large object by value copies the whole thing: every character, every element. For a function that only reads its argument, that copy is wasted work. Passing by reference avoids it, and adding const promises (and enforces) that the function won't modify the argument:
#include <iostream>
#include <string>
bool ends_with(const std::string& s, const std::string& suffix) {
if (suffix.size() > s.size()) {
return false;
}
size_t start = s.size() - suffix.size();
for (size_t i = 0; i < suffix.size(); i++) {
if (s[start + i] != suffix[i]) {
return false;
}
}
return true;
}
int main() {
std::cout << ends_with("report.pdf", ".pdf") << ends_with("a.txt", ".pdf") << ends_with("x", "long") << "\n";
}
100
The everyday rule
- Small, cheap types (
int,double,char,bool, pointers): pass by value. - Anything bigger (
std::string, containers, your classes): pass byconst&when the function only reads. - Pass by non-const
&only when the function is meant to modify the argument.
A const& parameter can also bind to temporaries and literals: ends_with("report.pdf", ".pdf") works because C++ creates temporary strings for the call. A non-const & can't bind to a temporary.
Size checks first
ends_with checks lengths before indexing. s.size() - suffix.size() on unsigned sizes would wrap around to a huge number if the suffix were longer, and the loop would read out of bounds. Guard clauses like this prevent exactly the kind of bug you saw in the integer types module.
Your turn: write bool starts_with(const std::string& s, const std::string& prefix) without using any library starts_with.