.. _example-program: Example C++ Program ################### .. include:: /header.inc .. vim:filetype=rst spell: We have talked a fair amount about C++ and how it functions, but we have not built a significant program. It is time to correct that before we study the language in detail Your last lab project asked you to perform a simple piece of math, and you should have been able to figure that code out based on your previous language work, and a bit of searching on the Internet, or in your text. For this example, we will do something a bit different. We will build a multi-file program that generates a Star-Wars name for you, using a scheme available on the Internet. We will not be doing much math here, but we will demonstrate how C++ can manipulate strings in a simple way. Baby Steps ********** We will build this program in stages. This will show you exactly how I want you to build programs. We will not be sitting at a keyboard for long periods of time, pounding away, and then see if anything works. We will develop habits where most of the time, things do work, just not completely. This is a simple idea, and amazingly effective at raising the enjoyment level in your programming work! Step 1: Getting Started *********************** We begin every program with a basic "Hello, World" pattern. I choose to modify the message so it identifies the program I am running. Something like this: .. literalinclude:: code/main1.cpp :linenos: :caption: main.cpp Since we are programmers, we do not want to waste time typing in a bunch of commands to build our programs. We will need a neat tool that has been around a long time, to help with building and testing our code. Go read up on this tool here: * :ref:`make-intro` Here is a `Makefile` that will process this code. :Makefile: .. literalinclude:: code/Makefile :linenos: .. note:: There are commented lines you will need to change to make this work on a PC. Simply comment out the current lines by adding a "#" character to each one, and remove that character from that following lines. Windows needs an executable (TARGET) named "example.exe", and uses "del" to remove files. Linux/Mac systems use "rm -f" to remove files, and there is no extension on executable files. .. warning:: When typing in a Makefile, any of those indented command lines, which follow some line with ``something:`` at the beginning, need to be indented with a real tab character. You know you have one if you "hop" to the left when you press the left arrow key at the beginning of the text. If you try to copy and paste the example Makefile, you will have spaces in those indent spots. Delete them, then type Ctrl-v then the tab key. Assuming you typed things in correctly, you should be able to do this: .. code-block:: text $ make $ ./example Star Wars Name Generator .. note:: On a PC, you can just type in "example" (no quotes) to run the program. Also note that the prompt on a PC will be different: something like ``C:\cosc1337\example1>``. I am simplifying all that by just using a "$" character for the prompt in my examples. The Problem Statement ********************* Here is what we want our program to do. George Lucas was rumored to have a scheme for generating a Star Wars character name based on a formula that looks like this: First Name ========== Your Star Wars first name is formed by the following formula: 1. Take the first three letters of your last name 2. Add the first two letters of your first name Last Name ========= Your Star Wars last name is formed by the following formula: 1. Take the first two letters of your mother's maiden name 2. Add the first three letters from the name of the town in which you were born. So for me, this is the relevant data: * First Name: Roie * Last Name: Black * Mother's maiden name: Larson * Town in which I was born: Washington, D.C. So my Star Wars name is Blaro Lawas Sounds pathetic, but I guess it will do! Step 2: Reading Data ******************** From our simple beginning, we need to add something to deal with part of this problem. It looks like we are going to have to process strings, so let's see if we can modify our code to read a string from the console. To use strings, we have to access the standard ``string`` class. We get access to this by including a new header line. Once that line is in place, we can use a new data type: ``string`` and set up a container to hold strings, and read a string in from the console, just like we do for other data types. Here is our next test program: .. note:: When we refer to the string data type name, we will need to add the ``std::string`` notation. Watch that! :main.cpp: .. literalinclude:: code/main2.cpp :linenos: And here is the run of this modification: .. code-block:: text $ make $ ./example Star Wars Name Generator What is your first name: Roie Hello Roie The prompt asking me to input my first name displayed the message, then left the cursor on that line. I typed in my name and pressed the `Return` key. The program then continued printing out the final message. Notice that I created a simple string variable, and just used the ``cin`` mechanism to get input. I could have typed in any string, but not one with a space in it: .. code-block:: text $ ./example Star Wars Name Generator What is your first name: This is a silly string with spaces Hello This Apparently, using this scheme, we only get the text up to something called "whitespace" which is made up of spaces and tab characters for this example. If we read something else, the rest of the line would be scanned, but we did not read more, so all that other text was just ignored. Hey, we are making progress. We can not move on and enter all four required pieces. Step 3: Gather Data ******************* Using the same pattern, we can set this code up to read in all four pieces of data from the user. Here is this version: :main.cpp: .. literalinclude:: code/main3.cpp :linenos: Notice that when the code lines got long, I folded them onto a second line so they would look nicer in the listing. I indented that second line so it would clearly not be another normal line of code, but a continuation of the previous line. This is how we write things when paying attention to style! The last line in this version is new. Formally, you are supposed to return an integer value when you exit the ``main`` function. It says so on the declaration line for that function. It is common to not do this, but if you want to be strictly correct, this last line is what you should use. The "EXIT_SUCCESS" says the program is terminating with no errors. If you want to halt the program indicating errors occurred, we can use a simple call to an "exit function that looks like this: .. code-block:: c return EXIT_FAILURE; The program will halt at this point and do no more processing. What are those return values? They are either 0 (no errors, or 1 ( errors). Operating systems, except Windows, can use this return value to decide to run additional programs in a "script" put together so the output from one program can be fed as the input to another one, and so on, until you get a final result. Unix (and Linux/Mac) use this to build complex programs out of small, utility programs on the system. Windows chooses to ignore this idea, so they ignore the return value. Here is a test run of this version: .. code-block:: text Star Wars Name Generator What is your first name: Roie What is your last name: Black What is your mother's maiden name: Larson What town were you born in: Washington, D.C Hello Roie Black You were born in Washington, and your mother's maiden name is: Larson Step 4: Slicing Strings *********************** We have all the data we need to complete our task. All we need to do now is figure out how to cut off the required parts of the strings. To do this, we need to do a little research into how strings are handled in C++. .. note:: We will explore this in more detail later. For now, I am going to Google "C++ string slicing" and see what pops up. Wow, that was easy. It turns out C++ has a neat method (another name for a procedure that can work on those "object" things you should have heard something about in your last class) that will cut off just the parts we need. In addition, C++ lets us join two string chunks to make a bigger string chunk using the simple "+" operator. Here is the code we need to add to our program: .. code-block:: c string part1 = last_name.substr(0,3); That first parameter is the index into the string (starting from zero) where we want our "substring" to start. The second parameter is the number of characters we want. Let's test this by adding this code: .. code-block:: text string part1 = last_name.substr(0,3); cout << "part1: " << part1 << endl; Now I see this in my output: .. code-block:: text Part1: Bla Looks good. We can now finish off carving off all of our needed parts: :main.cpp: .. literalinclude:: code/main5.cpp :linenos: :lines: 27-38 And here is the output. .. code-block:: text $ make $ ./example ... Part1: Bla Part2: Ro Part3: La Part4: Was Looks like I have all the chunks I need to build my final name. Except we have one problem. Each string starts with a capital letter. We do want two of those, but we need to lower-case the other parts. Looks like we need to do more Googling. This time we discover the ``tolower`` function. We further discover that we can treat a string as an array of characters, and use array indexing to modify the string. This will be easy! .. code-block:: c part2[0] = tolower(part2[0]); Do you see how this works. I only want to change the first letter, at index zero, so I access that letter, lower case it, and put it back into the string. How this magic works is something for later in the course. For now, we can build our next version that works as needed: :main.cpp: .. literalinclude:: code/main6.cpp :linenos: :lines: 38-45 And here is the final Star Wars name generated: .. code-block:: text Hello Roie Black You were born in Washington and your mother's maiden name is: Larson Part1: Bla Part2: ro Part3: La Part4: was Your Star Wars name is: Blaro Lawas Looks like we have a working program. Step 5: Is It Good Enough? ************************** We reached the end of our development, right? It seems to work, but is it good enough. Well... I am finding that as I get older, my typing has taken on a silly pattern. I seem not to be able to let go of the shift key as fast as I need to when typing, so I often get double capital letters, and this program will make a mess of that. To correct that, we need to add a few routines that deal with bad input data! What we need ============ To properly generate our names, we ought to lower case everything typed in, and upper-case only the first letter of both first and last names. To do this, we need to modify the code a bit, add two more functions, and new variables: Here is a function to lower case the entire string: .. literalinclude:: code/main7.cpp :linenos: :lines: 4-10 And, here is another routine to capitalize a string: .. literalinclude:: code/main7.cpp :linenos: :lines: 11-15 Adding these routines, and this chunk of code: .. literalinclude:: code/main7.cpp :linenos: :lines: 22-25,46-52 Now, running our code produces this output with really messy input! .. code-block:: text ... What is your first name: RoIe What is your last name: BlAcK What is your mother's maiden name: LArSoN What town were you born in: WasHiNgTOn Hello RoIe BlAcK You were born in WasHiNgTOn and your mother's maiden name is: LArSoN Your Star Wars name is: Blaro Lawas I could have processed the raw input to make the output look nicer, but I am displaying what the user typed in, so it must be their fault if it is ugly, right? Not always, but we will leave it at that for this example! Step 6: Factoring Our Code ************************** Programs that sit in one file are hard to maintain, and things cannot easily be reused. Although this program is short, we can break it up into several chunks, then make sure we can put everything back together to make it run. We will set up four chunks: * The "main" function, to control the action * The "data" function, to gather our input data * The "starWars" function, to generate the name * A "utility" function, that includes helper routines. Breaking Up =========== The "right" way to break things up in C++ is to place functions and variables you want to group together into a separate file, then create a "header" file that describes the things in that file. You will include the header file in any code that wants to use the routines found there. These header files have a specific pattern we will consider now. Utility Routines ================ Let's start off by placing the two utility functions we added to deal with messy data into a file by themselves. Here is that file: :utility.cpp: .. literalinclude:: code/utility.cpp :linenos: I added comments to explain what is in this file. The associated header file contains just enough information that the compiler can figure out how to call these routines in another program. We need to provide the name, parameters, and return type for each routine, but we do not need to see the code. The user of these routines will include this header in their code, then call these functions properly. It will be up to the linker to tie the parts together properly. .. literalinclude:: code/utility.h :linenos: What is all of the "#ifndef" stuff? ----------------------------------- Anything that starts with a "#" on a line is part of something called a "preprocessor directive". There is a program called a preprocessor that reads your source code files and copies each line to another hidden file, before the actual compiler gets to work. The compiler will eventually read this new file. When it sees a line starting with that sharp character (or hash as it is sometimes called), it stops reading your file and checks what this line says to do. Here are the possibilities: * `#include ` - find a system header file located with the compiler. * `#include "filename"` - find a file with this name in the current directory and reads that file in. * `#ifndef name` - check a table of "defined" names. If the indicated name is not defined, continue, otherwise skip everything up to and #endif line and continue after that. * `#define name` - add this name to the list of "defined" names. We use this scheme to prevent the compiler from reading a header line more than one time. The convention is to use the file name in all caps as the name you define, with an underscore replacing the dot. Just follow this convention and your code will work fine. .. note:: Modern C++ compilers do not need this kind of "header guard" to stop the compiler from reading this file more than once. In more modern C++ code you can just add this line at the top of your code, and none of the other lines shown above: .. code-block:: c++ #pragma once I really like this, so on future lecture I will switch to this style. Data Routines ============= Our data routine needs to read in all those user strings, and store them away. It will be hard to do that in a separate file, then make the names visible to other code in different files, but we can do it, if we do a simple trick. Here is our Data file: .. literalinclude:: code/data.cpp :linenos: :caption: data.cpp Do you see the trick? I made the input strings "global" meaning they are outside of any functions. That normally makes them visible to any code in this file, but we can make those variables visible to other code in other files by adding one keyword in our header. That word is ``extern`` and it tells the compiler that these names live in another module. The linker will take care of things later. And here is our header file: :data.h: .. literalinclude:: code/data.h :linenos: Star Wars Name Generator ======================== Our last file will generate the Star Wars name using the global variables we set up in the data file. Here is the code we need: .. literalinclude:: code/starWars.cpp :linenos: :caption: starWars.cpp And here is the header file: .. literalinclude:: code/starWars.h :linenos: :caption: starWars.h Modified Main Function ====================== Now that we have pulled most of the code out of our old main file, we are left with this: .. literalinclude:: code/main.cpp :linenos: :caption: main.cpp Modified Makefile ================= We need to modify the Makefile to deal with this new setup. Although we could just list all of the `.cpp` files, and it would build., it is better if we add a "dependency" for each `.cpp` file we process to build the associated `.o` file. I added something to make each `.o` file depend on the associated `.h` file. Obviously, we do not have a `main.h` file in this project, but ``make`` silently ignores that issue and builds the program anyway! Neat! Here is the new `Makefile`: .. literalinclude:: code/Makefile2 :linenos: :caption: Makefile If you copy-and-paste all of these files into a project folder and make your program, this is what you should see (well, if you are me!) .. code-block:: text Star Wars Name Generator What is your first name: ROie What is your last name: Black What is your mother's maiden name: LaRson What town were you born in: Washington Your Star Wars name is: Blaro Lawas I made a few typos on purpose here, just to show things are really working as before! Now we are done. Or are we? Can we improve it more? You decide! .. vim:filetype=rst spell: