Hello again! In this video, we are going to look at character functions.
C has a number of functions which operate on single characters.
C++ has put these in the cctype header.
To start off with, there is a family of functions which tell us the properties of a character. So we can
find out if it is a digit, 0 to 9, lowercase letter, a to z uppercase, whitespace character, space,
tab, newline etc, punctuation character, and so on.
So here is a simple example of that. We have a string and we have a range for loop. So this will go
through every character in the string.
So c will be each character in turn.
And then we call these functions with that character as argument.
I suppose you could argue these should be "else if" rather than just "if", but it is ondly a demonstration.
So 'H' is uppercase, 'e', 'l', 'l, 'o', are lowercase. Comma is punctuation, space is white space and so on.
If you have used Linux, or possibly some command line programs on Windows or DOS, you may have seen programs
that ask you to type in anything beginning with 'y' to confirm that you want to proceed.
So here is a function that can do that. It is going to use the toupper function. toupper() and tolower() will convert
the arguments to upper or lower case. Or they will return the equivalent, rather. They do not actually modify
the arguments.
And then we have this function.
So this will take the input that was entered by the user. Then we get the first character of that
input.
And then we get the uppercase version.
So if 'c' was lower case 'y', it will now be upper case 'y'. If it was uppercase 'y', it will still be uppercase
y. And if it was something else, it will still be something else. Maybe uppercase something else, but
it does not matter.
So if the result is equal to upper case 'Y', then the user typed in something beginning with 'y' or
uppercase 'Y' and we return true.
And if they did not, they typed in something else and we return false.
So here is that function. We get the input.
We take the first character. We see if it is equal to lowercase 'y' or uppercase 'Y', and we return accordingly.
And then there is a main function. We prompt the user and read their input into a standard string.
Then we call our function with that input as the argument and we print out a suitable response.
We are using a raw string here, just to avoid putting back slashes in front of these double quotes.
Do I want to enter a string which starts with capital 'Y' or lowercase 'y'?
Yes, I do!
Evidently I do!
Let's try...
YES.
I will take that as a "no"!
Okay, so that is how it works.
When we are dealing with C++ strings, the library regards them as being case-sensitive.
So if you have a string which has one with a capital 'O' and one with a lowercase 'o', those will be regarded
as having different data.
And there is no direct support for doing things which ignore case.
There is no function you can call, which says that those are actually the same data, just with a different
case.
In C, compiler vendors provided their own functions, which did case-insensitive comparisons.
Usually they're called stricmp on windows and strcasecmp on Unix.
Not the easiest things to pronounce!
Obviously, these are not standard.
They are also not portable.
If you write a program on windows that uses stricmp and then decide you want to run it on
Linux, you have to change all these calls to strcasecmp.
And besides, they only work with C-style strings.
They do not support the C++ standard string.
So what can we do?
If we want to compare C++ strings without worrying about case, the easiest way to do it is to
convert them all to the same case and then compare them.
So we have a loop which goes over every character in the string.
This time we are using a reference to auto, because we want to modify this character.
Then we get the uppercase version of the character and then we modify the elements in the string.
So after this loop completes, all the characters in the string will have been converted to uppercase.
And we could just as well use lower case, provided we are consistent.
I think computers have always used uppercase. And lowercase came in later, so people tend to use uppercase.
So if you convert all the strings before you compare them, that will work.
However, it does modify the string.
It also means that you lose all the information you had about the case information. And in some applications
that might not be acceptable.
So the next simplest thing to do is to take a copy of the string and then convert that to single case and
then compare the copies of the strings.
But that has quite a lot of overhead. As we discussed, copying strings can take a lot of processor
time.
The other alternative is to have a go at writing our own function for comparing strings.
So let's see how we would do that.
The first thing we need to think about is the interface to our function.
We should have the same interface as the built in operator, so we can use it exactly the same way and
it is not going to confuse people.
Unfortunately, we cannot give it the same name as the built-in operator because that is already defined.
And there is a One Definition Rule in C++, so you cannot have two definitions of the same symbol.
So we have to call it something else.
So let's call it equal_strings. For the sake of argument.
So to have the same interface, it will take two strings by const reference. And return a bool.
And if the two strings are equal, by whatever criterion we use for defining quality, this will return true.
And if they are not equal, it will return false.
The next thing we need to think about is what exactly this function will do. The function will go through
each string, so it is going to compare the corresponding characters.
So the first element from one string and the first element from another string. And it is going to compare
pairs of elements from each string, until it finds a mismatch.
And if it finds a mismatch, the strings are not equal and it returns false.
If it does not find a mismatch, then they are equal, and it returns true.
It is probably not a good idea to start optimizing code straight away, but this is a very easy "win". If
the two strings have different things, then they must be different, so we do not need to actually do
any comparisons.
So we call the size member function. If the return values are different, then the strings have different
lengths and they are different strings. So we can return false straightaway.
And by the way the size member function doesn't actually go through the string like the C version does.
This will just look up the element counts in the string header.
So this is a very fast operation.
So if we get past this, we then know that the two strings have the same length.
And then we have our loop. We are going to look at the corresponding characters from each string and compare
them.
So we start off by getting an iterator to the first element in each string. We use cbegin()
because we do not want to modify the strings. And we can use auto to avoid having to work out the type
of the iterator and type it out.
Incidentally, if we do change our minds later on and we find that we do need to modify the strings, we
just call begin() here. Then we will not actually need to modify the type of these variables.
So that is one argument for auto; it does make refactoring the code easier.
Although there are risks to that. And then we do the comparison, which we will look at in a minute.
Then we increment these iterations so we go into the next element. And we keep on doing that until we
reach the end of the strings.
Actually, you could argue that we only need to do one of these comparisons, because we know that both
strings have the same length.
So they are both going to finish on the same iteration. But it is not a good idea to be too clever!
And then in the comparison, we are going to convert each character to uppercase and then we compare
them.
And if the characters are different after being uppercase, then they are actually representing different
letters of the alphabet.
So we have a mismatch.
So we dereference the iterator to get to the value of each character. On the first time, that will
be the first character in the first argument. And the first time through, that will be the first character
in the second argument, the right hand string.
And then we call toupper() to convert them to uppercase.
And then we compare.
And if they are different, we know the strings are different and we can return false immediately, because
we found a mismatch.
On the other hand, if we keep on going and we get to the end of the loop, then we know that the two
strings do not have any mismatches.
So they must be the same. And then we can return true.
So here is that function.
We have the
signature. So we take two strings by reference to const and we return bool.
We start off by comparing the lengths of the strings. And if they are different, then the strings must
be different.
So we return false.
Then we get the iterators to the first element of each string.
Then we compare the data in those elements, after converting it to uppercase.
And if it is different, we have a mismatch and we return false.
If they are not different, then we increment the iterators and we go on to the next character.
And we keep on doing that until we reach the end of the string.
You could actually write this as a for loop.
And if I was doing a real program, I probably would.
But for demonstration, it is easier to split everything up and do
it one step at a time.
And then if we get to the end of the loop without finding any mismatches, the strings must be equal.
So we return true.
I have written a simple main program to test this. So I am going to create a few string objects.
We have "one" with all lower case and "ONe" with some mixed case.
Then we have a completely different string, just as a kind of "control variable".
Then first of all, I use the library equals operator.
We are using the ternary operator.
So if a is equal to be, then we print nothing.
And if it is not equal, we print "not".
Then we go through and use the equal strings. So we call equal strings on these two strings.
And again, we print out the results.
So what do you think will happen?
So with the library equality operator, "one" and "two" are not equal, "two" and "ONe" arenot equal, of course, but
"one" with lowercase and "ONe" with mixed case are also not equal because the library operator takes case
into account.
And it regards these as having different data. With our equal_strings function, we get the same results
for those two. Which is good!
We have not messed anything up!
And we also find that "one" and "ONe" are equal, regardless of the case of the letters.
So that all works.
So that is
quite a lot of code, I mean - this is mostly comments, actually.
I've tried to comment this as clearly as I can.
Later on in the course, we will actually see that we can actually write this comparison as a single statement.
So it can be made much more concise.
So I keep throwing all these teasers! But we will get there, I promise.
So anyway, that is it for this video.
I will see you next time.
But meanwhile, keep coding!