Example C++ Program

Read time: 49 minutes (12465 words)

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:

main.cpp:
1
2
3
4
5
6
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) {
    cout << "Star Wars Name Generator" << endl;
}

Here is a Makefile that will process this code.

Makefile:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Makefile for Lecture 5 example program
# modify if runnng on PC (change comments where needed)
#
TARGET	= example
#TARGET	= example.exe

RM		= rm -f
#RM		= del

FILES 	= main.cpp data.cpp utility.cpp starWars.cpp

OBJS 	= $(FILES:.cpp=.o)

all:	$(TARGET)

$(TARGET):	$(OBJS)
	g++ $^ -o $@

%.o:	%.cpp %.h
	g++ -c $< -o $@

clean:
	$(RM) $(OBJS) $(TARGET)

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/Macs use “rm -f” to remove files.

Assuming you typed things in correctly, you should be able to do this:

$ make
$ ./example
Example Program from Lecture 5

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 of 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 built-in 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:

main.cpp:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char *argv[]) {
    string first_name;

    cout << "Star Wars Name Generator" << endl;
    cout << endl << "What is your first name: ";
    cin >> first_name;
    cout << "Hello " << first_name << endl;
}

And here is the run of this modification:

$ make
$ ./example
Example Program from Lecture 5

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 type in any string, but not one with a space in it:

$ ./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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char *argv[]) {
    string first_name;
    string last_name;
    string mothers_maiden_name;
    string home_town_name;

    cout << "Star Wars Name Generator" << endl;
    cout << endl << "What is your first name: ";
    cin >> first_name;
    cout << "What is your last name: ";
    cin >> last_name;
    cout << "What is your mother's maiden name: ";
    cin >> mothers_maiden_name;
    cout << "What town were you born in:  ";
    cin >> home_town_name;
    cout << endl << "Hello " << first_name << 
        " " << last_name << endl;
    cout << "You were born in "<< home_town_name << endl;
    cout << "and your mother's maiden name is: " << 
        mothers_maiden_name << endl;
    return EXIT_SUCCESS;
}

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 line of code, but a continuation of the previous like. 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 like 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:

exit(RETURN_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:

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:

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:

string part1 = last_name.substr(0,3);
cout << "part1: " << part1 << endl;

Now I see this in my output:

Part1: Bla

Looks good. We can now finish off carving off all of our needed parts:

main.cpp:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    part1 = last_name.substr(0,3);
    part2 = first_name.substr(0,2);
    part3 = mothers_maiden_name.substr(0,2);
    part4 = home_town_name.substr(0,3);

    // lower case the first letters of part 2 and part3
    part2[0] = tolower(part2[0]);

    cout << "Part1: " << part1 << endl;
    cout << "Part2: " << part2 << endl;
    cout << "Part3: " << part3 << endl;
    cout << "Part4: " << part4 << endl;

And here is the output.

$ 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!

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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    // lower case parts 2 and 4
    part2[0] = tolower(part2[0]);
    part4[0] = tolower(part4[0]);

    cout << "Part1: " << part1 << endl;
    cout << "Part2: " << part2 << endl;
    cout << "Part3: " << part3 << endl;
    cout << "Part4: " << part4 << endl;

    star_wars_name = part1 + part2 + " " + part3 + part4;
    cout << "Your Star Wars name is: " << star_wars_name << endl;

And here is the final Star Wars name generated:

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, and add two more functions, and new variables:

Here is a function to lower case the entire string:

1
2
3
4
5
6
string LowerCase(string data) {
    for(int i=0; i< data.length(); i++) {
        data[i] = tolower(data[i]);
    }
    return data;
}

And, here is another routine to capitalize a string:

1
2
3
4
string Capitalize(string data) {
    data[0] = toupper(data[0]);
    return data;
}

Adding these routines, and this chunk of code:

1
2
3
4
5
6
7
8
9
    string part1, part2, part3, part4;
    string sw_first_name, sw_last_name;
    string star_wars_name;
    // handle bad input
    sw_first_name = Capitalize(LowerCase(part1 + part2));
    sw_last_name = Capitalize(LowerCase(part3 + part4));

    star_wars_name = sw_first_name + " " + sw_last_name;
    cout << "Your Star Wars name is: " << star_wars_name << endl;

Now, running our code produces this output with really messy input!

...
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 to whatever code 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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <string>
using namespace std;

#include "utility.h"

// utility string functions for normalizing bad name input

string LowerCase(string data) {
    for(int i=0; i< data.length(); i++) {
        data[i] = tolower(data[i]);
    }
    return data;
}

string Capitalize(string data) {
    data[0] = toupper(data[0]);
    return data;
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#ifndef UTILITY_H
#define UTILITY_H

// utility string functions for normalizing bad name input

string LowerCase(string data);

string Capitalize(string data);

#endif

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 <filename> - find a system header file located with the compiler.
  • #include “filename” - find a file with this name in the current directory and red that file in
  • #ifndef name - check a table of “defined” names. If the indicated name is not defined, continue, otherwise look for the #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.

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:

data.cpp:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <string>
using namespace std;

#include "data.h"

string first_name;
string last_name;
string mothers_maiden_name;
string home_town_name;

void get_data(void) {
    cout << endl << "What is your first name: ";
    cin >> first_name;
    cout << "What is your last name: ";
    cin >> last_name;
    cout << "What is your mother's maiden name: ";
    cin >> mothers_maiden_name;
    cout << "What town were you born in:  ";
    cin >> home_town_name;
}

void display_data(void) {
    cout << endl << "Hello " << first_name << 
        " " << last_name << endl;
    cout << "You were born in "<< home_town_name << endl;
    cout << "and your mother's maiden name is: " << 
        mothers_maiden_name << endl;
}

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 key word 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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifndef DATA_H
#define DATA_H

// Data input routines

#include <string>
using namespace std;

extern string first_name;
extern string last_name;
extern string mothers_maiden_name;
extern string home_town_name;

void get_data(void);
void display_data(void);

#endif

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:

starWars.cpp:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Star Wars Name Processor
#include <string>
using namespace std;

#include "starWars.h"
#include "utility.h"
#include "data.h"

string gen_name(void) {
    string part1 = last_name.substr(0,3);
    string part2 = first_name.substr(0,2);
    string part3 = mothers_maiden_name.substr(0,2);
    string part4 = home_town_name.substr(0,3);

    // handle bad input
    string sw_first_name = Capitalize(LowerCase(part1 + part2));
    string sw_last_name = Capitalize(LowerCase(part3 + part4));

    return sw_first_name + " " + sw_last_name;
}

And here is the header file:

starWars.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#ifndef STARWARS_H
#define STARWARS_H

// Star Wars Name Processor

#include "starWars.h"

string gen_name(void);

#endif

Modified Main Function

Now that we have pulled most of the code out of our old main file, we are left with this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <string>
using namespace std;

#include "data.h"
#include "starWars.h"

int main(int argc, char *argv[]) {
    cout << "Example Program from Lecture 5" << endl;

    get_data();         // process user data
    string sw_name = gen_name();
    cout << "Your Star Wars name is: " << sw_name << endl;

    return EXIT_SUCCESS;
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Makefile for Lecture 5 example program
# modify if runnng on PC (change comments where needed)
#
TARGET	= example
#TARGET	= example.exe

RM		= rm -f
#RM		= del

FILES 	= main.cpp data.cpp utility.cpp starWars.cpp

OBJS 	= $(FILES:.cpp=.o)

all:	$(TARGET)

$(TARGET):	$(OBJS)
	g++ $^ -o $@

%.o:	%.cpp %.h
	g++ -c $< -o $@

clean:
	$(RM) $(OBJS) $(TARGET)

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!)

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!