Recall that we said that a string is an object that combines some methods (functions) like size() with data. Well, the data a string needs to store is a list of characters. The characters in a string are indexed, or numbered, starting from 0:
The first character, 'H', is at index 0. Index 1 refers to the second character. This may be a little confusing at first, but it is a common way of numbering things in programming. (A good way to remember this that also hints at the reason for the conventionโthe first character is 0 bytes from the start of the string.)
To access characters in a string, string objects provide a function char at(size_t index). Given a location in the string, it returns the char at that index:
If we read carefully, we can see the error message tells us there was an out_of_range problem using the value 100 in a string with size() (which is 11). Notice however that it does not tell us the line number the error occurred on. If it was not obvious we would need to use a debugger or print statements to identify the location. Here, we can tell that the error happened before the line that prints "Well, that didn't work." as we do not see it in the output.
A common source of error involving strings and other arrays is indexing out of bounds. This is usually the result of forgetting to subtract 1 from size. Asking for index 11 in this string would cause an error.
There is another way to work with the characters in strings. Instead of myString.at(index) we can say myString[index]. The โbracket notationโ (the [ ]) works the same as .at(), except for one critical difference. When you use a bad index with the brackets, no error is generated. Instead, your code reads from or writes to whatever is in memory either before or after the stringโs characters. This can cause your code to modify some other variable, or some other important part of the code of your program!
.at() is โsafeโ because it prevents you from accidentally doing bad things without knowing about it. [ ] is dangerous because it does whatever you ask it to, even if that makes no sense.
Why is there an unsafe version? It can be slightly faster to not stop and check if the index makes sense before using it. But only VERY slightly faster. You would have to be doing many thousands of string operations before the speed difference became noticeable to a person. And compilers can often detect from what your other code is doing that the safety check isnโt needed and skip it.
C++ has a reputation for being a โdangerous languageโ because it doesnโt always protect programmers from their own mistakes. One way to make the language safer is to avoid the โdangerousโ way of doing things unless we have a very good reason not to. For this reason, we will only use .at() notation in this book.