Going from top to bottom we see:

#include <string>

This is the library in which the new data type 'string' is found. The type string is a class (which we'll study shortly). Unlike the built-in types (short, bool, double, etc.), class types often own functions in addition to being able to use certain operators.

Recall that cout and cin are objects (variables) of class types. cout's class owns at least precision() and flush(). And cin's class owns many functions: ignore(), ignore(n,ch), peek(), get(), get(&ch), fail(), clear(), etc. In addition to these functions, they can be used with certain operators: << and >> in particular. (Not all operators work, though. It wouldn't make any sense to add two input streams, after all. Just like % doesn't work for double because who needs a remainder when you have decimal places to store that information?)

In this program, we'll see several of the functions that the string class owns as well as some operators that work with the string class.


Next we come to these:

void no_middle(const string & whole, string::size_type f_len);
void has_middle(const string & whole, string::size_type f_len);

Here we are passing string objects to functions by constant reference. Recall that a constant reference passes as fast as a reference (not needing to copy the data) but is not allowed to change the original (thanks to the const). We called this a 'fast-by-value' passing before and that's just what we are using it for here. Since a string object might contain many, many characters, we don't want to make copies of it arbitrarily -- only when we must. By using constant reference here, we've avoided a copy we may not have to make.

Then there's that 'string::size_type' bit. Recall that the :: syntax denotes that the identifier on the right is inside of whatever is on the left. Here we are saying that size_type is inside of the class string. (Earlier we'd used this to note that certain functions were inside of the namespace std from our library interface files. The same syntax is used whether the identifier on the left is for a class or a namespace.)

We also see from the positioning that size_type must be a data type since it is used as the type for the argument f_len (on both functions). This is indeed the case. It is a type that is defined to be large enough to store all length/position information for a string object. It will be long enough no matter what system we are working on: Windoze, DOS, *nix, etc. That makes it portable should we need to use our program on a different platform someday. size_type is returned from many of the string class' functions that deal with the length/size of a string or with position information of characters within the string.


Next we find:

    string first, last, whole;
    char yes_no;

Here we've declared 3 variables of type string: first, last, and whole. These three objects will be empty to start with (like "" -- an empty string) but can be used to store string data later on. We also declare a simple char type variable, presumably for storing a yes/no response. This seems odd. If a string object can store multiple characters, why not just store the word "yes" or the word "no"? Why keep using those single char variables? In a word (no pun intended): efficiency. Many operations we perform on string variables are slow and space consuming. char takes 1 byte of storage and is dealt with quite quickly. We will only use string's when we need to handle an entire word (or more) -- like to store the user's first and last names.


Shortly there-after we have:

    cout << "Enter your name (first last):  ";
    cin >> first >> last;

This code uses three strings: a literal string for the prompt and our variables first and last. From this code we see that we can both output strings and input strings. The rules for output and input of strings is the same as for the built-in types. Output adds no extra spacing or labels for you -- you must do so yourself. (Here we've placed the spacing we desired inside our string an the literal string is serving as a type of labeling: a prompt.) Input will skip any leading spacing and collect legal data for the type until illegal data or spacing is reached. Since a string contains a group of characters, and char can contain any typed data, the only thing that will stop it is after-data spacing. Good thing the prompt told the user to put a space between their first and last names and we know they'll hit 'Enter' ('\n' -- a type of spacing) after they're done.


And some more operators follow:

    whole = first + ' ' + last;
    cout << "Oh, " << whole << ", is it?  Pleased to meet you!\n";

We see an assignment statement working with string objects. So we can assign strings. On the right side of the assignment operator, we see string and char being 'added'! If we think about it a bit, the only sensible thing for this to mean is concatenation (appending the right operand to the left operand to form a new string). So, what is happening here is that we are attaching a space to the end of the user's first name to get a temporary string. Then the user's last name is attached to the end of this temporary string to get another temporary string. Finally, this second temporary string is assigned into the whole variable. (As with numerical addition, the temporary results are thrown away once they are no longer needed. This is one source of inefficiency with the string class, but one we are not prepared to avoid just yet.)

Then we see a long cout statement with both string literals and a string variable. (Just in case you didn't believe me when I said they worked alike...*grin*)


Now we move on to:

    cout << "Do you have a middle name, " << first << "?  ";
    cin >> yes_no;
    cin.ignore(numeric_limits::max(), '\n');

Here we demonstrate the efficiency of char vs. string. By asking cin for the first non-whitespace char to store in yes_no and then having it ignore the rest of the line, we've avoided creating a copy of all the characters on that line just to look at the first one. Depending on what the user typed, this could be quite a savings indeed!


Almost done with the main we find:

    if (toupper(yes_no) == 'N')
    {
        no_middle(whole, first.length());
    }
    else
    {
        has_middle(whole, first.length());
    }

Here we check our yes/no char to see if they didn't or did have a middle name. Either way, we call one of our previously prototyped functions. Notice that passing a string actual argument is just like passing a built-in type actual argument -- just mention the variable's name. Since both our functions took their string parameter by constant reference, the whole variable won't be copied, but simply referred to in the function. But we won't be changing their name since the parameter is also const.

We also see our first string class function: length(). This function is called just like those belonging to the input and output stream classes: an object on the left, a dot, and the function call on the right. Here we want to know the length of the user's first name (we'll find out why when we see the definitions in a second) so we have first, dot, and length(). Recalling that all information about the length of a string object is stored in string::size_type, we know that the length() function must return this type. Since the second parameters of our functions were both string::size_type, this matches up perfectly!


In our first function (well, after main), we see:

void no_middle(const string & whole, string::size_type f_len)
{
    string middle, wh_name = whole;

Again, we pass the first argument by constant reference and the second argument is of type string::size_type and represents the length of the user's first name (hence the catchy argument name 'f_len').

Once inside the function, we declare two new string variables: middle and wh_name. middle is once again going to be an empty string to start and we'll modify it later. But wh_name is initialized to be a copy of the formal argument whole. We've seen this sort of thing from time to time with built-in typed variables and now we know it works with string class objects, too!

(On a side note, if we made a copy of the whole parameter, why didn't we simply pass it by value? Can we not pass string class objects by value? No. It was simply a choice this programmer made. It is often better to assume constant reference and then later make your own copy than to assume by value and then not need a copy.)


Now that we have variables we find:

    middle = "Alowicious";
    wh_name.insert(f_len+1, middle + ' ');

Here we assign the middle name variable to be the literal string "Alowicious". This could have been done when we declared the object originally, but it is a slightly different form of assignment than we'd seen before, so I thought we should experiment.

Next we use a new string class function: insert(p,s). The first argument to insert is the position where we want to insert something. Since the thing we want to insert is the new middle name (and a space after it), we want it to follow the first name (and it's space). Luckily we asked the caller for the length of the first name as our second argument (f_len) and we know that there is just the one space after it (+1). The second argument to insert is the string we want to insert. Here that's our new middle name and a space to separate it from the last name (the space originally between the last name and first name is going to be between the middle name and first name, now). (Again, the string we are inserting is a temporary result of the plus -- or concatenation -- operation between middle and ' '.) And the string we want to insert into goes on the left of the dot: wh_name.

After that, there's just more output and some tricky rhetoric to kid with the user before we return to the main (which then returns to the OS).


Now on to the second (and last) function:

void has_middle(const string & whole, string::size_type f_len)
{
    string middle, wh_name = whole;
    cout << "What is it (*blink sweetly* *tilt head*)?  ";
    cin >> middle;

Same sort of setup as the first function -- right down to the parameters and local variables. (Remember, it is often better to take a constant reference that we then copy than to take a copy which we never change.)

But, instead of assigning middle a value ourselves, we now read it from the user. (This function gets called when they said they did have a middle name, remember?)


After we know their middle name:

    wh_name.insert(f_len+1, middle + ' ');
    cout << '\'' << wh_name << "' is it?  That's just awful!\n\n";

We insert it into the whole name (wh_name) just as we did. All the discussion from before still applies. The only thing new looking here is that '\'' thing in the output. We've seen escape characters before ('\n', '\t', '\a', etc.). This one is just saying that the contents of this single char literal is a single quote. But so the compiler doesn't get confused as to which single quote ends the char literal and which is the contents, we escape the content single quote. So the compiler reads: start char literal ('), escape sequence contents (\), contents is a single quote ('), end char literal ('). (And if you ever need to place a double quote inside a string literal, you can use \" to do that...)


Now we see a new function and a new operator:

    wh_name.replace(f_len+1, middle.length(),
                    (middle != "Alowicious" ? "Alowicious" : "Barnabus"));

Let's start in the function's argument list with the new operator: !=. Here we are comparing the middle object with the string literal "Alowicious" to see if they are not equal. If they are, indeed, different, the ?: will result in the string "Alowicious", otherwise, it will result in the string "Barnabus". So, if the user told us their middle name was Alowicious, we'll get Barnabus. If the user had a middle name different from Alowicious, we'll get Alowicious. (And, yes, since you can use != on string's, you can also use ==, <, <=, >, and >=. The inequalities compare by ASCII-betical order. It's like alphabetical order, except that lower-case and upper-case are considered different. Upper-case come before lower-case, in case you've forgotten.)

Now the new string function: replace(p,n,s). The first argument is a position, here we are replacing their actual middle name with one we choose, so the replacement position is the same as the previous insertion position. The second argument is the number of characters to replace. Since we want to replace the middle name they told us, this is the length of their middle name. Finally, the third argument tells what to fill in instead of those n characters at position p. That would be the result of our ?: which will be either Alowicious or Barnabus depending on whether their middle name was already Alowicious or not. (It would be silly to tell them that 'Alowicious' sounded funny and then suggest that as the replacement!)


And that's all for this program. More later!