★ wanayoo — archive 1999 http://wilma.cs.brown.edu/courses/cs032/resources/C++tutorial.htmlNouvelle recherche | Portail wanayoo

CS 123
Java to C++ Transition Tutorial
(modified for CS 32)

Table of Contents

  1. Introduction
  2. Hello World
  3. C++ Classes
  4. Variables, Pointers, and Memory
  5. Memory Management
  6. Types, Values, and Expressions
  7. Standard Template Library basics (STL)
  8. Flow of Control
  9. Iteration
  10. The Command Line
  11. The Preprocessor
  12. Build Process
  13. Debugging Tips
  14. Miscellaneous Tips
  15. Makefiles

1. Introduction

Unlike Java, C++ is a fast, powerful, and flexible programming language. It was originally developed by Bjarne Stroustrup at what was then AT&T Bell Labs in the early to mid 1980s. The C++ programming language is derived from the C programming language. It attempts to retain as much of C's syntax as possible while adding most of the OOP features that you know and (have been brainwashed to) love. In that vein, C++ is a very large and complex programming language, designed to support many different programming paradigms. As such, C++ contains tons of features that you probably should never use, and it has many pot-holes that you must learn to avoid.

Java was built as a simple-to-learn subset of C++ for set-top boxes and drooling AOL users. Now, it's time to play in the big leagues.


About this tutorial

This Java to C++ transition tutorial gives a overview of the C++ programming language, focusing on the most commonly used features of the language. No guide of this length could begin to discuss the intricacies of this robust language, and this guide does not purport to do so. Instead, it gives students with a background in Java and object-oriented principles a brief yet somewhat thorough introduction to the language. Code examples are used in abundance in order to increase exposure to C++'s syntax and style. Aside from covering the basics of C++, related topics such as debugging tips and makefiles are discussed in brief. This is a short tutorial, not a reference; you will most likely need to gain access to one of the recommended books if you intend to program in C++ for any substantial period of time.

This tutorial was created in the fall of 1997 for use in CS123. It has been slightly modified for CS032. Modifications mostly involve changing from C conventions to STL conventions. This includes using cout instead of printf and using vectors instead of straight arrays. Any comments should be directed to the CS032 TA staff. The CS123 version of the tutorial is here. This tutorial can be used elsewhere as long as the CS123 staff of Brown University is credited for its creation.


Books and references

Below is a list of several good books that you should either read or refer to if you have any questions.


2. Hello World

For an introduction, let's take a quick look at the canonical first program, "Hello World!", in both Java and C++. If we examine a Java application instead of an applet, the two programs are very similar.

[Hello.java]
package hello;      // says that we are part of a package named hello

public class Hello  // declare a class called Hello
{
    public static void main(String args[])  // declare the function main
                                            // that takes an array of Strings
    {
        System.out.println("Hello world!"); // call the static method
                                            // println on the class System.out
                                            // with the parameter "Hello world!"
    }
}

[Hello.C]
#include <iostream>  // include declarations for the "cout" output stream

using namespace std;    // the cout stream is in the std namespace
                        // this tells the compiler to look in the std
                        // namespace, you can also write std::cout

int main(int argc, char *argv[])  // declare the function main that
                                  // takes an int and an array of strings
                                  // and returns an int as the exit code
{
    cout << "Hello world!" << endl; // this inserts "Hello world!"
	                    // and an newline character into the cout
						// output stream
}

Pretty similar, eh?

You will already notice a few key changes. The first is that there can be global functions, functions which are not methods of a class, such as main. The next thing you may see is that we have a #include statement. This tells the compiler to read in a file that usually contains class or function declarations. Third, notice that the Java program includes a package declaration, whereas C++ has no analagous concept of packages.

Finally, in Java the main method does not return a value, whereas in C++ it returns an integer. In C++, the integer returned is known as the exit code, which signifies whether or not the program terminated successfully. A value of 0 indicates success, and any other value means the program failed. If no value is explicitly returned, it will automatically return a value indicating success.


3. Classes

There are quite a few differences in syntax between how classes and functions are declared in Java and C++. The biggest difference you will notice is that while all function definitions are included in the class declaration in Java, they are usually put in separate files in C++.

First, in Java:

[Foo.java]
public class Foo        // declare a class Foo
{
    protected int m_num; // declare an instance variable of type int

    public Foo()        // declare and define a constructor for Foo
    {
        m_num = 5;       // the constructor initializes the m_num
                        // instance variable
    }
}

Then, in C++:

[Foo.H]
class Foo               // declare a class Foo
{
public:                 // begin the public section
   Foo();               // declare a constructor for Foo
protected:              // begin the protected section
   int m_num;            // declare an instance variable of type int
};

[Foo.C]
#include "Foo.H"

Foo::Foo()              // definition for Foo's constructor
{
   m_num = 5;            // the constructor initializes the m_num
                        // instance variable
}

We split the program into two files, a header file (which we gave the extension .H) and a program file (which we gave the extension .C). The header file contains the class declarations for one or more classes, and the program file contains method definitions. The program file includes the header so that it knows about the declarations.

Separating the program declaration and definition into two files has several distinct advantages. First, you can easily look at a header file and see the interface for a particular class, without being having to see its implementation. Second, separating the header and program files can speed program compilation when the implementation of a class changes.

The scope operator :: is used when declaring methods. If I have a class called Foo and it has a method called myMethod, when defining the function in the .C file, I would call it Foo::myMethod. The scope operator is needed because a .C file could contain method definitions for multiple classes, so we need to know for which class each method is being defined. In the example class below, we can see the scope operator in use:

[Foo.H]
class Foo {
public:
    Foo();
    ~Foo();
    int myMethod(int a, int b);
};  // note the semicolon after the class declaration!

[Foo.C]
#include "Foo.H"
#include <iostream>

Foo::Foo()  // scope operator :: helps define constructor for class Foo
{
    cout << "I am a happy constructor that calls myMethod" << endl;
    int a = myMethod(5,2);
    cout << "a = " << a << endl;
}

Foo::~Foo()
{
    cout << "I am a happy destructor that would do cleanup here." << endl;
}

int Foo::myMethod(int a, int b)
{
    return a+b;
}

It's crucial that you remember the semicolon at the end of a C++ class declaration. Failure to include the semicolon will cause a compile-time error, but not at the end of the class declaration. Often the error will be reported in a perfectly viable file, such as in a header file that you included.


Constructors and initializer lists

When an instance of a class is created, you frequently want to initialize various instance variables, some of which are objects. In Java this is easy: you can initialize those variables and perform other startup tasks in the constructor. In C++ you can do the same. C++ constructors can take a variety of parameters as in Java, plus there are some special constructors that we will discuss later. In addition, you can initialize instance variables in an initializer list before the rest of the constructor is called. Whether you use initializer lists for this purpose is partially a matter of personal preference. However, you will need to know its syntax: it's needed sometimes, such as when calling superclass constructors.

For the header file Foo.H:

[Foo.H]
class Foo
{
public:
   Foo();
protected:
   int m_a, m_b;
private:
   double m_x, m_y;
};

The following two definitions for Foo's constructor are functionally equivalent:

[Foo.C] // with initializer list
#include "Foo.H"
#include <iostream>
using namespace std;

Foo::Foo() : m_a(1), m_b(4), m_x(3.14), m_y(2.718)
{
    cout << "The value of a is: "  << m_a << endl;
}

OR

[Foo.C] // without initializer list
#include "Foo.H"
#include <iostream>
using namespace std;

Foo::Foo()
{
    m_a = 1; m_b = 4; m_x = 3.14; m_y = 2.718;
    std::cout << "The value of a is: "  << m_a << endl;
}

Useless trivia: The order in which the instance variables are initialized is not the order in which they appear in the initializer list, but instead the order in which they are listed in the class declaration.

Useful trivia: Don't use this inside an initializer list. It doesn't point to this.

In case you're wondering: Learning how to initialize objects requires some concepts and syntax you haven't learned yet. See the variables and memory management sections for more information.


Destructors

If you were paying attention to the first example in this section, you may have noticed that we declared a method Foo::~Foo in addition to Foo::myMethod and the constructor Foo::Foo. The special method is called a destructor and is executed when an instance of the class is destroyed. We will discuss it in more detail when we reach the memory management section.

Both constructors and destructors do not return anything. In addition, destructors take no parameters.


Protection

Like in Java, there are 3 levels of protection for class members in C++: public, private, and protected. They act pretty much the same way as they do in Java. Unlike Java, C++ has no notion of package friendliness, as there are no packages. Because of this, protected members are only accessible to subclasses, while in Java the whole package can use protected members. As you've probably noticed, you put members in sections by their protection level. You can have as many sections of each protection level in a class declarations as you would like. If no modifier is specified, the protection level defaults to private.

C++ also has an additional form of control over protection levels called friendship that allows for a finer grain of protection. You will probably not need to use this in the majority of your coding career. To find out more, consult one of the recommended books.


Inlining

Inlining is a way to make make your program faster. We will not be covering this in detail. If you are interested please look in one of the recommended books or consult a TA.


Overloading

In Java and C++ you can have more than one function with the same name. C++ uses the types of the parameters to determine which version of the function to call. There are all kinds of rules about when C++ will do implicit casts and other fancy things for you, but if you don't feel like spending a few weeks with Stroustrup learning about them right now, simply avoid ambiguity when you do overloading. How do you do that? If possible only overload on the number of parameters as opposed to the types of the parameters until you have learned all the rules. Or, if possible, call the functions by different names to avoid overloading entirely (OpenGL uses this method).

#include <iostream>
using namespace std;

void Foo::print(int a)
{
    cout << "int a = " << a << endl;
}

void Foo::print(double a)
{
    cout << "double a = " << a << endl;
}
On an instance "foo" of type "Foo", calling
foo.print(5);
will output
int a = 5
whereas
foo.print(5.5)
will output
double a = 5.5

Hint for later: When you learn about pointers and start overloading things so they take either a pointer type or an int, the symbol NULL is actually an int! This has brought down many a great C++ programmer. The workaround is to explicitly cast NULL to the pointer type you want.


Default parameters

You can give default values for parameters of functions in the .H file. If fewer parameters are passed than the function takes, it will use the default values. Using default values can sometimes help you avoid overloading functions or constructors. Note that parameters without default values must precede all the parameters with defaults; you can't skip arbitrary parameters in the middle of a function call. For example:

class Foo
{
public:
   Foo();
   void setValues(int a, int b=5)
protected:
   int m_a, m_b;
};

void Foo::setValues(int a, int b)
{
    m_a=a;
    m_b=b;
}

If we have an instance "foo" of class "Foo" and we did the following:
foo.setValues(4);
it would be the same as if we had coded: foo.setValues(4,5);


Inheritance

Inheritance in C++ and Java is pretty similar. Suppose we have a class B that inherits from a class A:

class A
{
public:
   A();
};

class B : public A
{
public:
   B();
};

This says that B has a public superclass A; there are types of inheritance other than public, but they are never used in real programs.

If you want to pass a parameter to the superclass constructor, you can do it in the initializer list:

[Foo.H]
class A
{
public:
   A(int something);
};

class B : public A
{
public:
   B(int something);
};

[Foo.C]
#include "Foo.H"
#include <iostream>
using namespace std;

A::A(int something)
{
   cout << "Something = " << something << endl;
}

B::B(int something) : A(something)
{
}

Not bad at all, eh? Umm, that is, as long as you don't use multiple inheritance. Multiple inheritance can be a big can of worms so if you think you need to use it or want to learn where to use it please see a TA first.


Virtual functions

To better explain virtual functions, examine the following example in Java:

public void someMethod() {
   Object obj = new String("Hello");
   String output = obj.toString(); // calls String.toString(),
                                   // not Object.toString()
}

The method toString() is defined in class Object and overridden in class String. In the above example, Java knows that obj is really of type String, so at it calls the String.toString() method. (This is polymorphism at work.) It can resolve which method to call at run-time since in Java, all methods are virtual. In a virtual method, the compiler and loader (or VM) make sure that the correct version of the method is called for each particular object.

As you can imagine, making everything virtual by default adds a little overhead to your program, which is against C++'s philosophy. Therefore, in C++ functions are not virtual by default. If you don't declare a function virtual and override it in a subclass, it will still compile even though the "correct" version of the method may not get called! The compiler may give you a warning, but you should simply remember to do this for any function that you may override later.

The virtual keyword, the opposite of the keyword final, allows you to say that a function is virtual:

class A
{
public:
   A();
   virtual ~A();
   virtual void foo();
};

class B : public A
{
public:
   B();
   virtual ~B();
   virtual void foo();
};

We advise making almost all methods virtual when writing your code, since making functions virtual usually adds a very small overhead to your program.

Also, you should always make your destructor virtual. If you do not do this then when you call delete the wrong destructor might get called.


Pure virtual functions

Java provides the keyword abstract to declare that a method is abstract or pure virtual. C++ also provides for making methods pure virtual. To do this, add the code = 0 after the parameter list in the function declaration.

For example, here are the Java and C++ equivalents of making a method pure virtual. First, in Java:

public class Foo
{
    public abstract int abstractMethod();
}
And then in C++:
class Foo
{
public:
    virtual int abstractMethod() = 0;  // The "virtual" and "= 0" are the
                                       // key parts here.
}

Just like in Java, a class derived from Foo cannot be instantiated unless all pure virtual functions have been defined. Also like Java, intermediate abstract subclasses that don't define their parent's pure virtual methods need not list them in their header file.


Overriding and scope

Say we have a class A and its subclass B. Say that they both have a virtual function foo and B wants to call A's foo. In Java, you would use the super command to use A's foo from B's. However, C++ has multiple inheritance, so we need another way to specify which foo to call. The scope operator :: allows us to do this:

[Foo.H]
class A
{
public:
   A();
   virtual void foo();
};

class B : public A
{
public:
   B();
   virtual void foo();
};

[Foo.C]
#include "Foo.H"
#include <iostream>
using namespace std;

A::foo()
{
   cout << "A::foo()" << endl;
}

B::foo()
{
   cout << "B::foo() called" << endl;
   A::foo();
}

So, if we have an instance "b" of class "B", calling
b.foo();
will output
B::foo() called
A::foo() called


4. C++ Variables and Memory Management

In C++, variables are declared in exactly the same way as in Java. Declaration of an integer variable would look like this under both languages:

int myNumber;

You can also assign values to local variables at the time of declaration, just as in Java:

int myNumber = 0;

(Instance variables can not be assigned a value when declared in the header file; they are initialized in the constructor instead.)

As you see above, C++ and Java declare base type variables in basically the same way. When it comes to declaring variables that can hold a class, however, things get a little more interesting.

Before we go on, we must talk some about memory.


Memory

In the introductory courses, Java has shielded you from dealing with computer memory directly, and your TAs did not go too much into it.

A computer is made up of many distinct parts. Among these, the most important ones are the CPU (central processing unit) and memory. If you have a CPU and memory, and throw in some sort of I/O (input/output) device, you have a simple, yet functional computer.

As you might guess, the memory device allows a computer to "remember" things, such as programs and data. The computer remembers everything as numbers in binary form, ones and zeros, on and off switches. A single binary digit is called one bit of information. Computers store everything as bits, including larger data such as strings and classes. Such data types are represented as binary numbers (groups of bits) and stored that way.

You may wonder how a program you write can be compiled into a meaningful series of ones and zeros that your computer understands. Well, it's the job of the compiler to take your program, parse it, and reduce it to special binary numbers called machine language instructions that the CPU on your computer understands. When you run your program, the computer loads these instructions into memory and executes them.

How does memory work?

Memory can be thought of as a very large number of "slots." Each slot holds 8 bits, or one byte. The computers you will be working on have 256 megabytes of memory, meaning they have about as many memory "slots." To organize all these slots, you can think of the computer as arranging them in a list. Slot 0 is at the beginning, slot 1 follows it, and so on, until there are no more slots.

---------------------
|      Slot 0       |
---------------------
|      Slot 1       |
---------------------
|      Slot 2       |
---------------------
        .
        .
        .
---------------------
|      Slot n-2     |
---------------------
|      Slot n-1     |
---------------------

A key thing to realize is that all slots have a unique number associated with them. Referring to "slot 5" is always talking about the same slot.

As mentioned before, each of these slots can hold a single byte. So if we stick a byte into each of those slots, we can say things like "I want to add the byte in slot 7 to the byte in slot 20," or "I want to copy the byte in slot 100 to slot 200." (Such commands are represented by one or more machine language instructions.)

Now that you know how bytes are stored in memory, the next question is how bigger things are stored. Integers, for example, take 32 bits to store. Well, they are just stored as 4 consecutive bytes. Larger types, such as classes, are similarly stored in consecutive memory slots. The computer stores a class in memory by turning it into several numbers. These numbers contain the values of the instance variables in your class and other such information. This class is then stored in memory in a series of consecutive slots.

We can do things with classes that we did with the numbers above. Just as we could say "add the number in slot 7 to the number in slot 20," we can say about classes, "take the class starting at slot 5 and do something to it." Since a class takes several slots, we deal with them in terms of the first slot they occupy. The compiler keeps track of how large each class is so that it knows how many slots after the initial one are used.


Pointers

What is a memory address?
A memory address is the number of one of the slots mentioned above.

What is a pointer?
A pointer is a memory address.

So, a pointer to an integer myInt is "the number of the slot that stores myInt," or more commonly, "the memory address of myInt."

How do you declare a pointer?

To declare a pointer to an integer, we place the star operator * between the data type and the variable name. For example:

int* myIntegerPointer;

One of the uses of the * is to tell the compiler that we want something to be a pointer when we are declaring it. So the line above means "I want a pointer to an integer" and not just an integer.

So, now we have a pointer to an integer. However, we didn't assign it a value, so right now it points nowhere. When you declare a pointer, it is pointing to nothing, or worse yet, it often points to a random slot. Therefore, you can not use a pointer without first giving it somewhere to point. Well, you can try using it, but your program will crash with a segmentation fault or a bus error.

How do you make a pointer point somewhere?

As we saw earlier, pointers point to data stored in memory. We need to get the memory address of some data in order to be able to make the pointer point to it. To get the memory address of something, we use the & symbol. One of its meanings is "address of."

Now, let's make an integer and have our pointer point to it.

int* myIntegerPointer;
int myInteger = 1000;
myIntegerPointer = &myInteger;

Let's do this in a little program and see what happens.

[main.C]
#include <iostream>
using namespace std;

int main(int argc, char **argv) {
    int myInteger = 1000;                // declare an integer with value 1000
    int * myIntegerPointer = &myInteger; // declare a pointer to an integer
                                         // and make it point to myInteger

    cout << myInteger << endl;           // print the value of the integer
    cout << myIntegerPointer << endl;    // print the value of the pointer
}

This program gives the following output:

1000
ffbef4d8

1000 is the value of the integer. ffbef4d is the value of the pointer in hexadecimal (4290704600), that is, the memory address of the integer.

How do you change the value to which a pointer points?

Now that we have a pointer to an integer, how can we put it to use? Well, suppose all we had was a pointer to the integer, and we wanted to change the value of the integer to which it points. Before you can actually say something like "set the value of the integer at memory address x to 50," you need to tell the compiler you are talking about the integer at address x, not the address x itself.

For instance, the code myIntegerPointer = 50 does not mean "set the number that myIntegerPointer points to to 50," but rather "set the value of myIntegerPointer to 50." This will change the memory address that myIntegerPointer actually points to; myIntegerPointer will now improperly point to "slot 50."

In order to modify the integer, we need to dereference the pointer before we use it. This is where the second use of the "*" comes in.

myIntegerPointer means "the memory address of <myInteger>."
*myIntegerPointer means "the integer at memory address <myIntegerPointer>."

Let's modify the example program to show this:

[main.C]
#include <iostream>
using namespace std;

int main(int argc, char **argv) {
    int myInteger = 1000;
    int *myIntegerPointer = &myInteger;

    // print the value of the integer before changing it
    cout << myInteger << endl;

    // dereference the pointer and add 5 to the integer it points to
    *myIntegerPointer += 5;

    // print the value of the integer after changing it through the pointer
    cout << myInteger << endl;
}

The output is:

1000
1005

This is the expected output. Initially, the number myInteger has a value of 1000. We then say *myIntegerPointer += 5, which means "add 5 to the number at memory address <myIntegerPointer>."

What happens if you dereference a pointer and store it in another variable?

Examine this code:

int myInteger = 1000;                    // set up an integer with value 1000
int* myIntegerPointer = &myInteger;      // get a pointer to it
int mySecondInteger = *myIntegerPointer; // now, create a second integer
                                         // whose value is that of the integer
                                         // pointed to by the above pointer

What will happen if we change the value of myInteger? Will the value of mySecondInteger change too? Let's see:

[main.C]
#include <iostream>
using namespace std;

int main(int argc, char **argv) {

    int myInteger = 1000;
    int *myIntegerPointer = &myInteger;

    // declare another integer whose value is the same as the integer
    // at memory address <myIntegerPointer>
    int mySecondInteger = *myIntegerPointer;

    // print the value of the first integer before changing it
    cout << myInteger << endl;

    // dereference the pointer and add 5 to the integer it points to
    *myIntegerPointer += 5;

    // print the value of the integer after changing it through the pointer
    cout << myInteger << endl;

    // print the value of the second integer
    cout << mySecondInteger << endl;
}

The output is:

1000
1005
1000

So, the answer is no: mySecondInteger is a wholly new integer at a different memory address. Changing the myInteger variable has no effect on the mySecondIntegerVariable. By assigning the value that the pointer points to to another variable, we have created a copy of that variable's value. Such a result is rarely intended, and we'll see where this can run you into trouble when we examine pointers to objects.

Let's print out the addresses of the two integers to be sure that we have a copy. To do this, we need to add the following two lines to the above program:

    cout << & myInteger << endl;
    cout << & mySecondInteger << endl;

The output is:

1000
1005
1000
ffbef4d8
ffbef4d0

As you can see, the addresses of the two numbers do actually differ.

Can more than one pointer point to the same address?

It is possible to have multiple pointers point to the same address. When this happens, changing the value of the number at that address changes the values the other pointers are pointing to, since it is the same address. Let's see an example:

[main.C]
#include <iostream>
using namespace std;

int main(int argc, char **argv)
{

    int myInteger = 1000;
    int *myIntegerPointer1 = &myInteger;

    // declare another pointer to the integer above
    int *myIntegerPointer2 = &myInteger;

    // declare a 3rd pointer. This time, however, make it equal to one of
    // the above pointers instead of getting the address again.
    int *myIntegerPointer3 = myIntegerPointer2;

    // print the values (addresses pointed to) of the pointers
    cout << myIntegerPointer1 << endl;
    cout << myIntegerPointer2 << endl;
    cout << myIntegerPointer3 << endl;

    // print the value of the number the pointers point to
    cout << *myIntegerPointer1 << endl;
    cout << *myIntegerPointer2 << endl;
    cout << *myIntegerPointer3 << endl;

    // let's change the number...
    myInteger = 5000;

    // ...and print the values of the pointers again
    cout << *myIntegerPointer1 << endl;
    cout << *myIntegerPointer2 << endl;
    cout << *myIntegerPointer3 << endl;
}

The output:

ffbef4d8
ffbef4d8
ffbef4d8
1000
1000
1000
5000
5000
5000

This shows that all the pointers do indeed point to the same address, and changing the number at that address affects the number all the other pointers point to as well.


Pointers to pointers

Since pointers are just numbers in memory - on our Sparcs, they're 32-bit integers - it's possible to have pointers to these pointers. To see this, let's first declare an integer and a pointer to it as we've done before:

int myInteger = 1000;
int* myIntegerPointer = &myInteger;

Now, let's declare a pointer to the above pointer myIntegerPointer. This will be a pointer to a pointer to an integer. A pointer to an integer is of type int *, so a pointer to that will be of type int **. Making it point to the pointer is a matter of assigning the pointer's address to our double-pointer:

int** myIntegerPointerPointer;
myIntegerPointerPointer = &myIntegerPointer;

If we now dereference myIntegerPointerPointer once, we have a pointer to an integer:

(*myIntegerPointerPointer) == myIntegerPointer == memory address of myInteger

If we dereference it twice, we get the integer again:

(**myIntegerPointerPointer) == the thing at memory address myIntegerPointer == myInteger

Creating an example program that demonstrates these equalities is an exercise for the reader.


Pointers to objects

Now that you know about memory and pointers, let's take a look at how we would declare variables to types other than integers. To start, assume that we have this simple class for the sake of later examples:

[Foo.H]
class Foo {
public:
   Foo();             // default constructor
   Foo(int a, int b); // another constructor
   ~Foo();            // destructor

   void bar();        // random method
   int blah;          // random public instance variable
};

To declare a variable to this class and create the class in Java, you could say:

Foo myFooInstance = new Foo(0, 0);

The above is not valid C++ syntax. The new operator returns a pointer to whatever follows it. The correct C++ syntax follows:

Foo* myFooInstance = new Foo(0, 0);

We have just made a pointer to an instance of type Foo and assigned it a value, the address of the instance.

Now, let's call the method bar on the instance. In Java, you would code:

myFooInstance.bar();

In C++ you can't do this, since myFooInstance is a pointer, and pointers need to be dereferenced before being used. To call a method through a pointer in C++, you would code:

myFooInstance->bar(); // dereference the pointer and call the method

Likewise, we can access public instance variables of instances of Foo. Of course, you would never do that. :)

myFooInstance->blah = 5;

The arrow operator -> does two things for you: it dereferences the pointer, and then it calls a method on the instance or accesses a member variable. This is shorthand for saying:

(*myFooInstance).bar();

which is basically carrying out the dereference and access steps individually. Since the arrow is shorthand for this, carrying out the two steps manually is almost never done.


Instances

In Java, the only way to create an object is to new one and store a reference to it in a variable. In C++, it is possible to declare objects without newing them explicitly. For example, here we declare a local variable of type Foo without using new and a pointer:

Foo myFooInstance(0, 0);

This line of code creates a variable of type Foo and passes the specified parameters to its constructor. If we wanted to create a Foo instance using the default constructor instead, we could say:

Foo myFooInstance; // same as Foo myFooInstance();

In Java, myFooInstance would be a null reference. In C++, it's an actual instance.

If we don't want to refer to the instance later, say, because it is being passed as a parameter, we can leave out the variable name:

// ... suppose the class Bar defines the method setAFoo(Foo foo) ...
Bar bar;
bar.setAFoo( Foo(5,3) ); // pass an instance of Foo

Calling methods and accessing public instance variables of an instance has the same syntax that you're used to in Java:

myFooInstance.bar();
myFooInstance.blah = 5;

Like pointers, instances may be local variables or member variables. If an instance is a member variable of a class, its constructor can be called in the class's constructor's initializer list, as in the following example:

[Bar.H]
#include "Foo.H" // must include Foo.H since we declare an instance of it

class Bar {
public:
    Bar(int a, int b);
protected:
    Foo m_foo; // declare an instance of Foo
};

[Bar.C]
Bar::Bar(int a, int b) : m_foo(a,b) // call Foo::Foo(int,int) and
initialize m_foo
{
    Foo fooLocal; // create another instance of Foo, this time as a local var
    // do something with the two Foos, m_foo and fooLocal
}

References

Suppose you allocate a chunk of memory for an object. Sometimes, it may be useful to refer to this block of memory with more than one name. We can sort of already do this with pointers, since multiple pointers can point to the same object. There is also a way to do it without using pointers; we can use something called references instead. Look at the program below to see how references can be used:

[main.C]
#include <iostream>
using namespace std;

int main(int argc, char **argv) {

    int foo = 10;
    int& bar = foo;

    bar += 10;
    cout << "foo is: " << foo << endl;
    cout << "bar is: " << bar << endl;

    foo = 5;
    cout << "foo is: %d\n" << foo << endl;
    cout << "bar is: %d\n" << bar << endl;
}

Here, we have allocated memory to hold an integer and named it foo in the first line. The & sign you see in the second line declares a reference to an integer variable. By assigning foo to bar, bar does not become a copy of foo, but instead refers to the same memory location as foo. When you change the value of bar, it also changes the value of foo and vice versa. References are essentially the same as pointers, except that they are dereferenced like instances, can never be NULL, and can only be assigned to once, at creation. The output of the above program should look like:

foo is: 20
bar is: 20
foo is: 5
bar is: 5

Since references can be assigned to only at creation, references that are members of a class must be assigned to in the constructor's initializer list:

[Bar.H]
class Foo;

class Bar {
protected:
    Foo & m_foo; // declare an reference to a bar
public:
    Bar(Foo & fooToStore) : m_foo(fooToStore) {}
};

References are used most commonly when dealing with parameters; see the parameters section for more information.


Converting between pointers and instances

You can convert between pointers and instances using the "*" and "&" operators that were mentioned above.

Example 1 (making a pointer from a local variable):

Foo myFooInstance(0, 0);     // create local variable instance of foo
Foo* fooPointer;             // declare a pointer to Foo classes.
fooPointer = &myFooInstance; // set the value of the pointer to be the address
                             // of foo.

Now, the following two statements have the same effect:

myFooInstance.bar(); // call bar through the instance
fooPointer->bar();   // call bar through the pointer

Example 2 (making a local variable from a pointer):

Foo* fooPointer = new Foo(0, 0); // create a pointer to Foo and give it a
                                 // value
Foo myFooInstance = *fooPointer; // dereference the pointer and assign it
                                 // to myFooInstance; copy is made (!)

The above code may not have the result that you expect. If you remember from earlier, we had an example of a pointer to an integer that we dereferenced and stored in a second integer variable. We discovered that the second integer was actually a copy of the first. A similar thing is happening here: the instance that fooPointer points to and myFooInstance are actually two separate instances.

The first line news an instance and assigns the address of that instance to the pointer. The second statement dereferences the pointer and assigns the instance to myFooInstance. Here, the compiler performs a bitwise copy of the instance pointed to by fooPointer and assigns it to myFooInstance, or, if you have defined a copy constructor, a copy of the class is created using that. So, saying fooPointer->blah = 5; would not change the value of blah in myFooInstance. Doing things like this yields really confusing code and is a potential source of really big, juicy bugs. For this reason, it is usually a bad idea to do this.

By the way, what is a copy constructor?

A copy constructor is a constructor that is invoked when one instance of a class is assigned to another instance, such as being copied when it's passed as a parameter.

The syntax for a copy constructor is:

class Foo {
   Foo(const Foo &classToCopy); // copy constructor
};

A copy constructor usually assigns all the values of instance variables in the class that is passed in to the instance variables that this constructor is called on.


5. Memory Management

Clearly, declaring and using variables is a major aspect of programming. The memory needed to store these variables varies with the type of the variable and where it is declared. There are two major categories of storage:


Local storage

The following block of code shows an integer and a instance of the class Bar being allocated in local storage:

{
    int myInteger; // memory for an integer allocated
    // ... myInteger is used here ...

    Bar bar; // memory for instance of class Bar allocated
    // ... bar is used here ...
}

The { and } symbols mark the beginning and the end of a block. When program flow enters the block, memory needed to store an integer is allocated for myInteger, and memory needed to store the class instance is allocated for the variable bar. When the end of the block is reached, this memory used to store myInteger and bar is freed up and those variables cease to exist. Trying to use the variables after the block is closed will yield compile errors, just as in Java.


Allocating memory with new

In the example above, we're out of luck if we want to use bar outside of its block. If we want to do this, we need to put bar in global storage instead. In C++, we can request a block of memory in global storage for certain data types by using new, and we return the memory by using delete.

As you've seen in the pointers section, the syntax for the new operator is as follows:

new ClassName(<initializer list>);

On success, a chunk of memory that is the size of the object is allocated and a pointer to that memory is returned. If the memory can not be allocated to store the instance, (which will most certainly never happen on our machines,) it returns a value of 0, C++'s representation of null. Note that if the class has a constructor that takes no parameters, the parentheses and the initializer list are optional.

The following C++ code shows how you can allocate memory and use it later:

[Bar.H]
class Bar {
public:
    Bar();
    Bar(int a);
    void myFunction(); // this method would be defined elsewhere (e.g. in Bar.C)
protected:
    int m_a;
};

Bar::Bar
{
    m_a = 0;
}

Bar::Bar(int a)
{
    m_a = a;
}

[main.C]
#include "Bar.H"

int main(int argc, char *argv[])
{
    // declare a pointer to Bar; no memory for a Bar instance is allocated now
    // p currently points to garbage
    Bar * p;

    {
        // create a new instance of the class Bar (*p)
        // store pointer to this instance in p
        p = new Bar();
        if (p == 0) {
            // memory allocation failed
            return 1;
        }
    }

    // since Bar is in global storage, we can still call methods on it
    // this method call will be successful
    p->myFunction();
}

Notice that you can still use the object generated by the new statement even if you are outside the block. You can see that except for pointer statements, this segment of code is the same as in Java. (Indeed, Java is doing exactly the same thing behind the scenes.)


Deallocating memory with delete

In Java, you allocate memory for an object using new, and a garbage collector frees the memory automatically when no existing object references it. In C++, you have to be much more responsible than that. Whatever memory you allocate in global storage, you must explicitly free, or your program will swell in size and contain what are called memory leaks. To avoid leaks, you need to keep track of all the memory you have newed and free it when you no longer need it.

Actually, it is very easy to free memory that you have newed. Use delete to deallocate the memory when you are done with it. For example, to free the memory allocated above, add this line at the end of the function:

delete p; // memory pointed to by p is deallocated

Remember that only objects created using new should be deleted with delete! Instances created in local storage are automatically recycled and should not be deleted explicitly. For example, the following code will make your program crash:

Bar bar; // bar not created with new
// ... use the instance of Bar ...

delete bar; // EEK! bar is in local storage...program crashes!

Managing memory: Classes

Technically speaking, deleting objects is pretty easy. So, it seems, it should be just as easy to avoid leaking memory in your programs. Unfortunately, this is not the case; people often write code that has leaks everywhere. Later, in the debugging section, we will introduce some methods for eliminating leaks. However, there is an easy and effective way of avoiding them: good programming style.

We mentioned class destructors earlier, but we didn't mention their use. In Java, you don't have to deallocate memory. You seldom need to fill in the finalize() method for an object. In C++, memory that is newed is not deallocated automatically, so you have to explicitly free it. Since you can free memory at any time your program is running, the question is when to do it. The following is a good rule of thumb: memory allocated in a constructor should be deallocated in a destructor, and memory allocated in a function should be deallocated before it exits.

The following C++ class definition is an example of poor memory management in a class:

[Foo.H]
#include "Bar.H"

class Foo {
private:
    Bar* m_barPtr;
public:
    Foo() {}
    ~Foo() {}
    void funcA() {
        m_barPtr = new Bar;
    }

    void funcB() {
        // use object *m_barPtr
    }

    void funcC() {
        // ...
        delete m_barPtr;
    }
};

Notice that in the above class, some memory is allocated when funcA is called. This memory is freed up when the function funcC is called.

Here is some code that uses the above class:

{
    Foo myFoo; // create local instance of Foo
    myFoo.funcA(); // memory for *m_barPtr is allocated

    // ...
    myFoo.funcB();
    // ...
    myFoo.funcB();
    // ...

    myFoo.funcC(); // memory for *m_barPtr is deallocated
}

The above code does not leak any memory. When funcA is called, we allocate some memory that is used internally by myFoo. Calling funcB then uses the memory. Finally, calling funcC frees up the memory. Since we have deleted all newed memory, this code contains no memory leaks.

Now, let's take a look at some code that uses the Foo class improperly:

{
    Foo myFoo;
    //...
    myFoo.funcB(); // oops, bus error in funcB()

    myFoo.funcA(); // memory for *m_barPtr is allocated

    myFoo.funcA(); // memory leak, you lose track of the memory previously
                   // pointed to by m_barPtr when new instance stored
    //...
    myFoo.funcB();

}   // memory leak! memory pointed to by m_barPtr in myFoo is never deallocated

The above snippet has a couple of errors. First of all, we call funcB before calling funcA. This means that the memory funcB operates on has not been allocated yet. This will cause a bus error, and your program will crash since m_barPtr is pointing to some random memory. Now assuming calling funcB first did not cause a crash, we proceed to call funcA two times in a row. The first time we call it, we allocate the memory and store it in a variable. The second time we call it, we allocate a new chunk of memory and assign it to the same variable again. This means that we have now lost the pointer to the previously allocated block of memory and have no way of finding it again. This causes a leak, since this memory can never be deallocated.

Now take a look at the class below, which uses a constructor and destructor correctly:

[Foo.H]
#include "Bar.H"

class Foo {
private:
    Bar* m_barPtr;
public:
    Foo();
    ~Foo();
    void funcA() {}

    void funcB() {
        // use object *m_barPtr
    }

    void funcC() {
        // ...
    }
};

Foo::Foo()
{
    m_barPtr = new Bar;
}

Foo::~Foo()
{
    delete m_barPtr;
}

Memory is always allocated in the constructor at the time a Foo object is allocated. The memory is automatically deleted when myFoo is deleted or goes out of scope. Using the constructor above, it is impossible not to allocate the memory before we call funcB, nor is it possible to forget to delete the memory, since the destructor is automatically called.


Managing memory: Pointers, references, and instances

After learning about pointers, references, and instances, you may have been wondering when each type should be used in your programs. Unfortunately, there is no hard and fast rule. What is important is that you know how memory is managed for each.

When dealing with pointers, you explicitly newed a class or something, tying up some memory. Unless you explicitly call delete after you are done using that instance, it will tie up memory until your program exits.

The memory where a local variable instance is stored, on the other hand, is automatically managed by the computer. When a local variable ever goes out of scope, the memory that it ties up will be freed up automatically. It doesn't matter if you have a pointer or reference to it somewhere; if it goes out of scope, it will be destroyed, and the pointers and references to it will point to nothing. Note that this is different from Java's garbage collection.

A reference can be thought of as just another name for the value to which it refers. Consequently, references are automatically managed by the computer, and you don't need to worry about deleting them or anything.


Managing memory: Parameters

As you know, in Java parameters are passed by reference. When you pass a reference to an object in Java, you can change the actual object by calling methods on it or accessing its public instance variables. In C++, parameters can be passed either by reference or by value. Think of passing by value as passing a copy instead of the real thing.

Here's an example of passing by reference. We define the function IncrementByTwo to take a reference to an integer. Since the function has a reference, it can alter the integer that is passed in to it:

void IncrementByTwo(int & foo) { foo += 2; }
You can increment an integer variable by calling:
int bar = 0;
IncrementByTwo(bar);

The variable bar will now have been increased by two.

Now, let's define the same function, only this time we will pass by value:

void IncrementByTwo(int fooVal) { fooVal += 2; }

If we use the same code above, bar will still be 0 after IncrementByTwo has been called. This is because the formal parameter fooVal contains a copy of bar. So, passing by value here will not give the result that we want.

A third way to pass fooVal is to instead pass a pointer to it. In this example, we define IncrementByTwo to take a pointer to the integer:

void IncrementByTwo(int* fooPtr) { *fooPtr += 2; }

We call the function by passing a pointer:

int bar = 0;
IncrementByTwo(&bar); // note the & sign; remember, we pass a pointer to bar

Since we passed a pointer to bar, it will be incremented by two just as we wanted.

So the question remains, how do we pass around objects in C++? Well, just as our integer bar above, objects can be passed by reference, by value, or by passing a pointer to the object. Since objects are often newed, meaning that you have a pointer to them, they are most commonly passed by a pointer. Of course, they can be passed by reference as well. However, objects are generally not passed by value, since that implies that a copy of the object is being made. For small types like integers, making a copy is not a big deal; but for objects, this can take up a lot of time. If you're passing by value to make sure that the object you passed in won't be changed, instead make the input parameter const. (See below for a description of const parameters.)

Return values

Return values can be passed in all the ways discussed above. Discussing return values, however, allows us to note a common C++ pitfall: passing a local variable outside of its scope. Above, we mentioned that variables in local storage are automatically destroyed when the block they are in closes. So, if you return a pointer or reference to a variable declared in this manner, and the variable leaves scope at some time, the pointer or reference will point to trash.

To see this, look at the following example:

[FooFactory.C]
#include "FooFactory.H"
#include "Foo.H"

Foo* FooFactory::createBadFoo(int a, int b) {

    Foo aLocalFooInstance(a,b); // creates an local instance of the class Foo
    return &aLocalFooInstance;  // returns a pointer to this instance

} // EEK! aLocalFooInstance leaves scope and is destroyed!

Here, we've created an instance of the Foo class, passing its constructor the input parameters of the createBadFoo method. We then get the memory address of this instance and return it. At this point, everything is fine: we have a pointer to the instance of Foo that we just created. At the next step, however, the function ends since we returned, causing aLocalFooInstance to be destroyed. Now, that pointer we returned is pointing to, well, garbage.

Note that this next example is flawed as well, since we return a reference to a local variable:

Foo& FooFactory::createBadFoo(int a, int b) {

    Foo aLocalFooInstance(a,b); // creates an local instance of the class Foo
    return aLocalFooInstance;  // returns a reference to this instance

} // EEK! aLocalFooInstance leaves scope and is destroyed!

The solution to this problem is to either return a pointer to an instance in global storage, or to return an actual instance:

Foo* FooFactory::createFoo(int a, int b) {
    return new Foo(a,b);  // returns a pointer to an instance of Foo
}

Foo FooFactory::createFoo(int a, int b) {
    return Foo(a,b);  // returns an instance of Foo
}

The moral of the story: never return pointers to variables you did not new, unless you can be completely sure that they will never leave scope.


6. Types, Values, and Expressions

Basic Java types such as int, double, char have C++ counterparts of the same name, but there are a few differences:


Enumerated types

In C++, you can define enumerated types using the enum keyword. Enumerated types are sometimes useful for expressing a value that has a limited range. For example, we might create an enum for the life cycle of a caterpillar:

enum CatLifeCycleType
{
   LARVA,
   CATERPILLAR,
   PUPA,
   BUTTERFLY
};

You can now create a variable of type CatLifeCycleType and assign to it values such as LARVA or PUPA.

Useless fact: By default, the values declared in an enum statement are assigned integer values starting at 0 and increasing. So, in the above example, LARVA represents the number 0, CATERPILLAR is number 1, and so on.

Useful fact: You may bypass this default numbering and assign each enumerated value an actual integral value. For example, redefining the above example:

enum CatLifeCycleType
{ LARVA = 1, CATERPILLAR = 2, PUPA = 3, BUTTERFLY = 4 };

Useful fact: An enumerated type can be cased off of in a switch statement.


The const keyword

In C++, the keyword const means different things according to its context. When you add const in front of a variable, it means that variable is treated like a constant. You will not be able to change the value of a const variable once you assign it. An example of its usage would be:

const float PI = 3.14156;

If an object is declared as const, then only the const functions may be called. If const is used with a member function, that means only const objects can call that function. For example, suppose you have the following class:

[Foo.H]

class Foo
{
public:
   void ChangeValue(int newVal) { m_val = newVal; }
   int GetVal() const { return m_val; }

   const float PI = 3.14156;

protected:
   int m_val;
};

If an instance of foo is declared as const, you cannot call ChangeValue on it. Correspondingly, since GetVal is declared const it cannot modify _val.

The const keyword can be used in another way that you might not expect. Parameters in a function may be declared const, which means that those parameters will not be changed during the function call. For example, consider the following function:

int multiply(const int a, const int b) { return a*b; }

Now, does this mean that only constants can be passed into multiply? No. Rather, it means that during this function, the parameters a and b will be treated as constants.

There are several reasons why it is good practice to use const whereever you can. One main reason is efficiency. When you pass an object by value to a function, the program needs to allocate memory for that object and make a copy of it. When you make the object a const parameter, it will not create a new copy of that object. Repeated function calls with const parameters are much faster then non-const parameters.

For example:

float GetRed(const int x, const int y)
is much faster than
float GetRed(int x, int y)

Secondly, declaring parameters and/or functions as const improves program readability. If you declare a function const, for example, anyone reading your code will know right away that the function doesn't change the object on which it is called. In addition, the compiler will return an error if a const function modifies its object, or if a const parameter is modified in its function.


Math expressions

People often make errors when they write numeric expressions. These are often the most notorious and most difficult bugs that can ever be present in your programs. Here are a few tips you should know:


7. Standard Template Library basics

The Standard Template Library (the STL) provides a way for you, the programmer, to use common data structures in an efficient and typesafe manner. It is important that you understand the rudiments of templates and templated classes in order to make use of the STL. A templated class only takes its final form at compile time. It uses parameters provided by the programmer to generate the templated code: in the STL, these parameters are usually type names. You've already seen other template patterns in mail-merges, spreadsheets, and mad-libs; just not in Java.

What is the advantage? In Java, data structures and other classes typically take a java.lang.Object or Comparable as paramaters in order to remain general. This means that you lose type safety unless you spend the time to write an adapter.

Using templates, types can be provided as parameters. The C++ compiler then uses the STL headers to generate code specific to the given types. For example,

[STLBasics.C]
// we will be using vectors, so we must include the standard vector header
#include <vector>

// we must use the standard ("std") namespace for the standard template library
using namespace std;

// ExampleClass will have nothing but a default constructor and destructor
class ExampleClass { };

// The main function where we will create and use STL vectors
int main(int argc, char **argv) {
   // "intvector" is a vector where each element must be an int
   vector<int> intvector;

   // The compiler has now automatically created a vector class
   // "intvector" that only takes type "int"

   // "examples" is a vector where each element must be an ExampleClass
   vector<ExampleClass> examples;

   // The compiler has now automatically created a vector class
   // "examples" that only takes type "ExampleClass"

   // these two assignments will work perfectly:
   intvector[0] = 6;
   examples[0] = ExampleClass();

   // these two will fail at compile time, which is preferable to failing
   // at run time:
   intvector[1] = ExampleClass();
   examples[1] = 8;
}

Now we have type safety and speed, all in one system. Templates are one of the most advanced and powerful features of C++; after you gain familiarity with the STL, you may wish to write your own templated classes when appropriate.

STL Documentation online! Right here at SGI.


STL Lists

Complete documentation for STL Lists

An STL list is just a simple linked list. Please refer to the SGI link for more detailed information. There are a few syntactical concepts we need to formally introduce at this point.

Speaking of the example, here it is; there is more explanation at the bottom:

[MyListHeaders.H]
// we need to include "list" in order to create STL lists of our own
#include <list>

class Bar
{
     // (your class goes here)
public:
     virtual void printBar() const;
};

class Foo
{
public:
     Foo();
     virtual ~Foo();

     // this will add a Bar* to the end of the list, and we now
     // assume that the Bar* belongs to this class. (so we will delete
     // it when the time comes)
     virtual void addBarToList(Bar *newbar);

     // this will get the first Bar* from the list
     virtual Bar* getFirstBar() const;

     // this will print out all of the Bar objects in the list
     virtual void printAllBars() const;
protected:
     // m_barList is a "list" with a template parameter "Bar*".  This is
     // the format used to declare templated variables.
     list<Bar*> m_barList;
};

[MyListDefinitions.C]
// get all of the declarations above
#include "MyListHeaders.H"

// iostream has the "cout" stream that we use to print to the terminal
#include <iostream>

// all of the STL (and "cout") is in the "std" namespace.  See the
// Stroustrup book for more information on namespaces.
using namespace std;

// (your Bar definitions could go here)
void
Bar::printBar() const
{
     // your bar printout code here
}

Foo::Foo()
{
     // nothing needs to be done -- list initialization is automatic
}

void
Foo::addBarToList(Bar *newbar)
{
     m_barList.push_back(newbar);
}

Bar *
Foo::getFirstBar() const
{
     // this will return the front element of the list
     return m_barList.front();
}

void
Foo::printAllBars() const
{
     // Please see the description below this code snippet to
     // understand this loop.

     for (list<Bar*>::const_iterator barIter = m_barList.begin();
	  barIter != m_barList.end();
	  barIter++)
     {
	  // const iterators return "const Bar*"s
	  const Bar *currbar = *barIter;

	  // print out the current Bar*.  This would generate
          // a compile warning if "printBar()" was not a const
          // method
	  currbar->printBar();
     }
}

Foo::~Foo()
{
     // Be careful about memory management here -- if anybody else uses
     // these Bar*s there will be big problems!

     // See the printAllBars method to better understand this for loop.
     // Since we need to modify the Bar*s, we can't use a const_iterator
     // this time.
     for (list<Bar*>::iterator barIter = m_barList.begin();
	  barIter != m_barList.end();
	  barIter++)
     {
	  delete (*barIter);
     }

     // often times we want to clear the list in the destructor. (in this case
     // it would happen automatically)
     m_barList.clear();
}


// the main function demonstrates how to use the class we've defined above
int
main(int argc, char **argv)
{
     Foo f;

     // Add a bunch of Bar*s to the Foo class.  If we kept references
     // to these Bar*s, we would be affecting the same objects that the
     // list refers to, of course. (Be sure you understand why!)
     f.addBarToList(new Bar());
     f.addBarToList(new Bar());
     f.addBarToList(new Bar());

     // Of course, we might want to do something to the Bar* before passing
     // it to the Foo:
     Bar *anotherbar = new Bar();
     // (Manipulate anotherbar here)
     f.addBarToList(anotherbar);

     // get the first and last bars in the list
     Bar *firstbar = f.getFirstBar();
     Bar *lastbar = f.getLastBar();

     // print out the first Bar
     cout << "First Bar:" << endl;
     firstbar->printBar();

     // print out the last Bar
     cout << "Last Bar:" << endl;
     lastbar->printBar();

     // print out all of the Bars
     f.printAllBars();

     // f's destructor will be called when it goes out of scope
}

So, what's going on in the Foo::printAllBars() method? Let's look at the for loop in detail; if you understand this, the STL is almost under your control. It is worth your time to study this method.

  • Since the method is declared as "const," we can only use const methods of our member variable m_barList. m_barList.begin() can return either a const_iterator type, or just an iterator type -- however, only the former is a const method, and so that's what the compiler forces us to use.
  • "What is an iterator again?" An iterator is a class that usually wraps around the parameter type. ("Bar*" in this case) It allows for the simple linear traversal of the data structure. A simple linear traversal helps in batch operations. (Printing out all of the "Bar*"s, for example)
  • To get the object that's carried by the iterator, use the "*" operator. That "*" operator has been overloaded to return the parameter type ("Bar*" in our case).
  • "What the hell is 'list<Bar*>::const_iterator'?!?" That's a good question. Succinctly, it's just a type, but we should be more specific. The "::" is just the C++ class delimiter. What does it delimit? Well, "list<Bar*>" is a class type, and "const_iterator" is a member of that class. In this case, the member is a typedef, not an int or some other traditional item. So, what we have here is "class-type::typedef barIter". See the SGI documentation for more information.
  • "m_barList.end()" is defined not as the last element, but one element past the last element. Thus, if our iterator extends far enough to equal m_barList.end(), we have reached the end of our list. You see lines like this a lot in STL iteration code.
  • "barIter++" is an overloaded "++" operator. It simply increments the iterator to the next item in the list. "++barIter" is also acceptable here -- whatever you prefer.

void
Foo::printAllBars() const
{
     for (list<Bar*>::const_iterator barIter = m_barList.begin();
          barIter != m_barList.end();
          barIter++)
     {
          // const iterators return "const Bar*"s
          const Bar *currbar = *barIter;

          // print out the current Bar*.  This would generate
          // a compile warning if "printBar()" was not a const
          // method
          currbar->printBar();
     }
}

STL Deques

Complete documentation for STL Deques

A Deque is essentially the same thing as the java.util.Vector you may already be familiar with. It does have some other features worth noting, namely O(1) front insertion. There is also an STL Vector, and its functionality is nearly identical.

To save space and confusion, you'll only see snippets of code here; for complete examples see the repository in /course/cs032.

#include <deque>
#include <iostream>
using namespace std;

int main(int argc, char **argv)
{
     // initialize the deque with a size of 10
     deque<int> intdeque(10);
     for (int count=0; count<10; count++) {
          // "intdeque.at(index)" is equivalent to the "my_array[index]" operator in java
	  intdeque.at(count) = count*10;
	  cout << "intdeque.at(" << count << ") = " << intdeque.at(count) << endl;
     }

     // make the deque bigger
     intdeque.resize(100);
     for (int count=90; count<100; count++) {
	  intdeque.at(count) = count*10;
	  cout << "intdeque.at(" << count << ") = " << intdeque.at(count) << endl;
     }

     // push a few elements on the front
     intdeque.push_front(32);
     intdeque.push_front(2002);
     intdeque.push_front(314159);

     // print out the first five elements again
     for (int count=0; count<5; count++) {
	  cout << "element " << count << " is " << intdeque.at(count) << endl;
     }
}

Strings

Complete documentation for STL Strings

Strings in the STL are similar to strings in Java. They have comparable properties and features, and they have handy internal reference counting and efficient copy constructors (Don't worry if you aren't familiar with these terms). The biggest single difference is the mutability of strings in the STL; Java strings cannot be modified after creation, whereas STL strings can be modified and even expanded.

C++ strings replace the "char*"s of C. A conventional char* is an array of characters, terminated by a zero character. For instance,

const char *test = "Testing\n"
Index: test[0] test[1] test[2] test[3] test[4] test[5] test[6] test[7] test[8]
Character: 'T' 'e' 's' 't' 'i' 'n' 'g' '\n' 0

Note that the terminating 0 is not the ascii character ('0'), but the actual int (0). The ascii escape code for 0 is '\0'. Ask a TA for more information about char*s generally, as this tutorial is determined to leave them behind. If you're using an STL string but you need a C-style char*, then use the "c_str()" method of the string class.

string mystr("Testing C++ -> C strings");
const char *my_c_string = mystr.c_str();

Here are some examples of how to use strings:

#include <string>
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
     // make a new string
     string s1("new string.");

     // make another new string, but in a different way
     string s2 = string("another string.");

     // you can append one string with another using the "+" operator
     string s3 = s1 + s2;

     // the compiler will automatically make an STL string out of the 
     // literal string below.  
     string s4 = s1+"  More appended text";

     // output all the strings
     cout << "s1: " << s1 << endl;
     cout << "s2: " << s2 << endl;
     cout << "s3: " << s3 << endl;
     cout << "s4: " << s4 << endl;

     // we can use the "at()" method as we did with the deque
     cout << "s4.at(4) = " << s4.at(4) << endl;
     s4.at(4) = 'Z';
     cout << "Modified s4.at(4).  Now s4.at(4) = " << s4.at(4) << endl;
}

The output of the above code is

s1: new string.
s2: another string.
s3: new string.another string.
s4: new string.  More appended text
s4.at(4) = s
Modified s4[4].  Now s4.at(4) = Z

STL strings are often passed by copy. STL strings are one of the few STL structures where copying is even "acceptable." (There are exceptions to all of these rules, of course, but generally you should make sure you know what you're doing before you copy STL structures) STL strings are internally reference-counted and only copy the data upon modification. Usually STL objects are passed by reference in order to ensure that caller and callee are using the same data structure.


STL Maps

Complete documentation for STL Maps

If you were asleep in your data structures class, a map is a structure that associates a key with a value. Maps in the STL allow for the efficient retrieval of values through the use of things like balanced binary search trees (which are conveniently hidden from view).

Maps use something called a "pair" to store their data. A pair is a templated class that has two members, "first" and "second". The map stores key/value pairs, and so the key is "first" and the value is "second." Is this confusing? A little bit, yes, but it greatly simplifies iterative tasks. (The next example illustrates this convenience)

The maps have a lot of features, but most programmers only use a few:

[MapExample.C]
#include <map>
#include <string>
#include <iostream>
using namespace std;

int main(int argc, char **argv)
{
     // the map "phonebook" has key type "string" and value type "int"
     map<string,int> phonebook;

     // make an entry for the sun lab
     phonebook["Sun Lab"] = 8637721;

     // make an entry for the graphics lab
     phonebook["Graphics Lab"] = 8637693;

     // make an entry for the TA Room.  This syntax is harder to read but
     // doesn't make use of any potentially confusing or misleading operator
     // overloading like the last two.  Use either version for insertion.
     phonebook.insert(pair<string,int>("TA Room",8637720));

     cout << "--> Printing out all elements in alphabetical order:" << endl;
     // iterate through all of the elements in the map
     for (map<string,int>::const_iterator iter = phonebook.begin();
	  iter != phonebook.end();
	  iter++) {
	  // grab the key and value from the iterator
	  string key = (*iter).first;
	  int value = (*iter).second;

	  // print out the key
	  cout << "Key: \"" << key << "\"";
	  // print out the value
	  cout << ", Value: " << value << endl;
     }
     cout << "--> Completed element printout." << endl << endl;

     // try to find the entry for the Sun Lab.  If there is no entry,
     // find returns phonebook.end().
     if (phonebook.find("Sun Lab") == phonebook.end())
     {
	  cout << "ERROR! \"Sun Lab\" not in phonebook." << endl;
	  exit(-1);
     } else {
	  cout << "Test Passed: \"Sun Lab\" is in phonebook." << endl;
     }

     // try to find the entry for the Systems Lab
     if (phonebook.find("Systems Lab") == phonebook.end())
     {
	  cout << "Test Passed: \"Systems Lab\" not in phonebook." << endl;
     } else {
	  cout << "ERROR! \"Systems Lab\" is in phonebook." << endl;
	  exit(-1);
     }

     // remove the entry for the Sun Lab, since we know it's present
     // from above
     phonebook.erase("Sun Lab");
     // ( note that you can also pass an iterator to erase() )
     
     // check if the Sun Lab has been successfully removed
     cout << "(Attempt has been made to remove \"Sun Lab\")" << endl;
     if (phonebook.find("Sun Lab") == phonebook.end())
     {
	  cout << "Test Passed: \"Sun Lab\" not in phonebook." << endl;
     } else {
	  cout << "ERROR! \"Sun Lab\" is in phonebook." << endl;
	  exit(-1);
     }

}

STL Memory Management Conventions

Using the STL takes a bit of getting used to. Even once you understand the syntax and semantics, though, you need to be smart about memory management. Here are a few basic guidelines. There are exceptions to all of these rules, but most of the time they're a good place to start from.


8. Flow of Control

Flow of control constructs are very similar in Java and in C++, with the difference being that Java has a strong bool type and C++ does not. In C++ conditional expressions (such as the expression that decides which branch to take in an if statement) are cast to integers. The counterparts of true and false in C++ are any expressions which evaluate to non-zero and zero values. This means that the type checker will not catch many typos you might make. For instance a common mistake is:

if (a = 3) {
    // do something
}
Here an assignment "=" was used instead of an equality comparison "==". For this reason it is often useful to put constants on the left when possible and to double check your conditional expressions.
The if statement

The syntax for this statement is the same in C++ and Java:

if(<predicate>)
   <do this if predicate is true (!=0)>
else
   <do this if predicate is false (==0)>

As in Java, the else block is optional.


The switch statement

The switch statement is the same as in Java also.

switch (<variable to case on>) {
case <value 1>:
     <stuff to do if above variable == value 1>
     break;
case <value 2>:
     <stuff to do if above variable == value 2>
     break;
case <value 3>:
     <stuff to do if above variable == value 3>
     break;
default:
     <stuff to do if none of the cases matches the variable>
     break;
}

The variable or expression that the switch statement cases on can be of any type whose equality can be tested using ==. Integers and enumerated types are two commonly used values in a switch statement.

In switch statements, when a case section is done executing, flow of control will fall through into the case below. To avoid this, always put a break statement at the end of each case, and save yourself confusion later. If you must have a case block fall through for some reason, make sure you comment it so you (and others) know it is intentional.

Like Java, you do not have to have a default case.


Boolean expressions

A predicate is any boolean expression, i.e. an expression that evaluates 0 for false, or non-zero for true. As far as syntax goes, C++ and Java predicates are identical. Just be sure to remember that zero means false and non-zero means true.

You can combine boolean functions in any order to generate predicates. Listed below are several boolean functions that you should know. The functions are listed in decreasing order of precendence, and "false" really means zero and "true" means non-zero:

!x               Returns false if x is true and vice-versa.
x < y            Returns true if x is less than y, else false.
x > y            Returns true if x is greater than y, else false.
x <= y           Returns true if x is less than or equal to y, else false.
x >= y           Returns true if x is greater than or equal to y, else false.
x == y           Returns true if x and y are equal, else false.
x != y           Returns true if x and y are not equal, else false.
x && y           Returns true only if both x and y are true.
x ^^ y           Returns true if either x or y is true (not both)
x || y           Returns true if one of x or y is true (or both)

All these operators can be combined in any way you want to generate complex expressions. Since it is easy to forget the precendence rules, always use parentheses in your expression to make it easier to read and to be sure that it does what you want.

Remember that testing for equality uses ==, not =. In Java, if you accidentally tried to test if two things are equal using =, you would get a compile-time error. In C++, you will not get a compiler error and your program will just behave unexpectedly! This mistake is very easy to make and hard to track down, so be as diligent as you can to avoid it.


9. Iteration

Loops in Java and in C++ are practically identical. Here is a list of different types and syntaxes:

The for loop

Syntax:

for(<initialize counters>; <loop condition>; <increment counters>)
   <statement>

For example:

for(int i = 0; i < 10; i++)
   cout << "I am counting to 10" << endl;

The loop body gets executed while the loop condition is true, and the loop terminates the first time it is false.

It should be noted that in some C++ compilers, including some on our system, the scope for a counter declared in the for loop definition is considered to be outside the loop. For this reason, the following code is valid in Java but could produce "Multiple variable declaration" compile errors in C++:

for(int i = 0; i < 10; i++) {
   // do something...
}
for(int i = 0; i < 10; i++) {
   // oops, counter i already declared...possible C++ compile error...
   // to correct, change this loop to read "for (i = 0; ...", or declare
   // i at start of function to avoid ambiguity
}

The while loop

Syntax:

while(<expression>)
    <statement>

The statement gets executed so long as the expression in the parentheses evaluates to true (non-zero). This is just like a for loop without a counter variable:

for(;<expression>;)
    <statement>

The do...while loop

Syntax:

do
  <statement>
while(<expression>);

In a do...while loop, the statement is executed before the expression is evaluated, so the statement is executed at least once, even if the expression is false. This differs from a while loop or a for loop in this respect.


10. The Command Line

As you already know, you can pass in command line arguments to a program when you execute it in a shell. Many shell commands take in command line arguments. One example is ls -l. Here, we are executing the program ls and passing it the command line argument -l.

How does a program read in command line arguments? Take a look at the following code:

[main.C]
#include <iostream>
using namespace std;

int main (int argc, char* argv[])
{
      cout << "Total number of arguments: " << argc << endl;
      cout << "Your executable name: " << argv[0] << endl;
      for (int i = 1; i < argc; i++)  {
         cout << "Argument # " << i << ": " << argv[i] << endl;
      }
}

The program will print out all the command line arguments passed to it. Notice how argc is used to control the number of strings (char*s) we read from argv. argv[0] will always be the name of the executable itself! argv[1] is the first command line argument, argv[2] is the second one, etc.

If you compiled this program and named the executable my_exec, a sample execution could produce the following output:

$ my_exec testa testb
Total number of arguments: 3
Your executable name: my_exec
Argument # 1: testa
Argument # 2: testb
$

Often times, you want to input numbers as command line arguments. However, all arguments are read in as strings. To convert strings to numbers the easiest thing to do is to use the stringstream class. Look at the int2string.C file in the strings repository.

As we saw in the Hello World program, the main function returns an integer in C++. In this program we return 0 (implicitly) since the program flow reached the end of the main function successfully.


11. The Preprocessor

Before the "real" compiler actually touches your program, a program called the preprocessor processes it. The job of the preprocessor is to do simple text substitutions and the like. Preprocessor commands all start with the # character. While many C programs have relied heavily on use of the preprocessor, one of C++'s goals has been to eliminate most preprocessor use. However, there are still a few things that you must know.


#include statements

The preprocessor allows you to include the contents of one file in another. This is performed using the #include statement. If the name of the file to be included is enclosed in angle brackets < >, the compiler will search a standard list of directories for the file you wish to include; you can add entries to this list with the compiler flag -I. On the other hand, if the name of the file is in quotes " ", it will search the current directory for the files in addition to the other directories. For example:

#include <math.h>
#include <iostream>
#include "MyHeader.H"

The #include directive is typically used to load header files. The following is a non-exhaustive list of times when you will need to include the header file for a class:

Including lots of header files in a header file is discouraged. See the section on forward declarations for more information.


#define statements

The #define preprocessor directive will tell the preprocessor to do text substitution. For example, the following directive will substitute every occurrence of FIVE with the number 5:

#define FIVE 5

The #define statement can also do substitution with parameters, allowing you to write simple macros. However, using #define macro substitutions can have many negative side effects. Since C++ has features that make using macros largely unnecessary, we advise that you avoid them. To declare constants, you could declare an extern const int in some header file and assign that int a value in some program file. To declare short functions, simply write inline code.

A macro can be undefined with the #undef directive.


Conditional compilation

You can use the preprocessor to conditionally compile code. The statements used to do this are #if, #ifdef, or #ifndef as well as a #endif following the conditionally compiled section. For example:

#define COMPILE_SECTION

// ...

#ifdef COMPILE_SECTION

// some code here
// this code would be compiled since COMPILE_SECTION is defined above

#endif

Conditional compilation can be used for avoiding circular includes (see below), writing code for multiple platforms, or optionally showing debugging messages.


Circular includes

In general, things can't be defined twice in C++. This means that two files cannot include each other. For example, if Foo.H does a #include "Bar.H" then Bar.H cannot #include "Foo.H". Even if the restriction on multiple definitions didn't exist, this would clearly lead to infinite recursion. Luckily, we can use conditional compilation to avoid this. All of your header files should have something like this:

[Foo.H]
#ifndef Foo_Header
#define Foo_Header

// put all of the header file in here!!

// remember this endif or you will have big, big problems!
#endif

The first time the file is read, Foo_Header isn't defined, so it defines it and reads the code in the header file. The second time, Foo_Header will already be defined, so your code will be skipped, making your class declaration defined only once.


Forward declarations

You've now learned how to prevent circular includes, but you might be wondering how to deal with two classes that actually need to know about each other. It turns out that you don't need to know anything about a class other than its name in order to declare a pointer to it. So, if a class contains a pointer to another class, instead of including the first class' header in the second's header, simply make a forward declaration. Then, include the header in the .C file:

[Foo.H]
#ifndef _FOO_H_ALREADY_INCLUDED_
#define _FOO_H_ALREADY_INCLUDED_

class Bar;       // forward declaration; says "class Bar exists, but we
                 // don't know anything about it"
class Foo {
// ...
protected:
   Bar* m_bar;    // We can declare a pointer to a bar.  We can't call
                 // any methods or declare a non-pointer bar until we
                 // include its header file.
};

#endif

[Foo.C]
#include "Foo.H"
#include "Bar.H" // must include here if we want to instantiate a Bar

// ...

In fact, forward declarations should not just be used to avoid circular includes. Using forward declarations instead of #include statements in your header files can drastically decrease the time it takes to compile your program after you change it. For this reason, use forward declarations as much as possible and avoid including header files unnecessarily. Your header files should have mainly forward declarations, and your program files should have mainly #include statements.


12. Build Process

This is just a short introduction to the entire build process of how your code goes from your .C and .H files into an executable. In Java, you simply run javac, and your .java files become .class files and you run your program using the Java Virtual Machine which handles most of what you must do manually in C++. This is why we use the utility called make, because it handles all of this build process for you. For more information, see CS31 or CS167.


13. Debugging Tips

There are several kinds of errors you can encounter in your code:

Compile errors

Compile errors are the easiest problems to tackle. Most of the time, they are typos or obvious mistakes such as passing the incorrect numbers of parameters to functions. More so than with Java, it's likely that each actual error in your code will cause several compile errors to be returned, possibly in multiple files. As you learn to program in C++ you'll learn what the compile errors are really telling you. In the beginning, solve your errors one at a time and try to compile again.

Run time errors

Run time errors can have many causes, the most notable (and obvious) among these are the ones which crash your program. Often times, stepping through your code manually is the quickest way to find these bugs. Failing that, a powerful debugger called dbx is at your disposal. dbx allows you, among many other things, to set breakpoints, to examine values of variables, and to check memory access. However, dbx is a cumbersome tool (especially for beginners) and may not be necessary to track down a run time error. If you think you know what part of your code caused the error, it might save you time by just checking the code directly.

Numerical errors

Numerical errors are caused by limitations of your underlying software and hardware implementation. For example, you can not have a char value greater than 255, and there is limited accuracy for floating point numbers. There is no way to eliminate a numerical error. However, you can anticipate the range of your numerical calculations and choose the proper variable types and algorithms. Numerical errors are very difficult to track down. The best approach for avoiding numerical errors is careful and incremental coding.

Algorithmic errors

Often, students spend time debugging their code over and over with out thinking, "Is it my code which is wrong or my algorithm?" If you have spent three hours debugging ten lines of code, most likely, the problem is the latter. On such occasions, stop coding! Go home, eat some food, take a shower, think about what could be going wrong with your code. Most times, you will shout out "Eureka!", like Archimedes, and run back to the Sun Lab to finish your program in 10 minutes. If you cannot figure out the problem, don't be afraid to ask a TA. We don't encourage people to come with a piece of code for us to debug at TA hours, but if you really put some serious thinking into the problem and still cannot figure it out come ask a TA on hours.

Memory leaks

Finally, when your program is just about complete, you should try to get rid of any memory leaks in your code. Memory leaks occur when you allocate memory with new that you fail to deallocate before the program quits. To find out if you have leaky code, you can use the bcheck utility. In a shell, type bcheck <executable_name> <program_arguments>. This will output a file named <executable_name>.errs that will contain a list of all your leaks, along with the file names and line numbers where they occured. (Memory leaks can be detected from dbx as well; in fact, bcheck is just a wrapper around dbx used for checking memory leaks.)

Additional information

The CS32 web site also has a collection of C++ resources and tools that are useful, including a list of common C++ compile errors.

The Answerbook has a section on dbx that can help you get started.


14. Miscellaneous Tips

This is just a collection of miscellaneous pieces of advice, amalgmated from students and TAs past and present about the use of C++ and things to keep in mind in general. Some of this may be repetitive, but on the other hand it's in an easy to reference location. If you have any suggestions on other things to add to this list, please let us know.


15. Makefiles

This is far too large a topic for the scope of a tutorial, make is a large and evil beast. Below, you will find a sample CS123 Makefile with the sections you can edit yourself marked. For more information on the make utility, see the Answerbook.

[Makefile]

Don't worry about this, just leave it.
.SUFFIXES: .H .C .lex .y

This is the name of your executable
EXECUTABLE = brush

These are the names of your objects (if you add a .C
file, you'll need to add something here, which is namely the name of the .C
file without the .C - for example, if you add MyFile.C, you need to add MyFile
to this line. If MyFile only has a header file (MyFile.H), you do not need to
add anything here.)
OBJECTS = main_brush

DEBUGFLAGS = -g0 -xildoff
FASTFLAGS = -fast -unroll=6

Pick the line you want for speed or debugging. 
COMPILEFLAGS = $(DEBUGFLAGS)
#COMPILEFLAGS = $(FASTFLAGS)
Only one of the lines above should be uncommented at a
time. The '#' symbol is a comment in Makefile-speak.

The three lines below tell make where to find the
C++ compiler: 
SUNPATH = /opt/SUNWspro6.0/WS6
SUNINC = $(SUNPATH)/include/CC
CCC = $(SUNPATH)/bin/CC

Don't touch this.
MAKEDEP = makedepend
SUPPLOC = /course/cs123/lib/I
OFILES = $(OBJECTS:%=%.o)

This line tells the compiler where to look for files
specified with #include<file>
IFLAGS = -I. -I$(SUNINC) -I/cs/include/motif -I/usr/openwin/include
-I$(SUPPLOC)

This line specifies where to look for libraries 
LFLAGS = -L$(SUPPLOC) -R$(SUPPLOC)

Do not touch anything below here.

all : $(EXECUTABLE)

$(EXECUTABLE) : $(OFILES)
    @echo
    $(CCC) -o $(EXECUTABLE) $(COMPILEFLAGS) $(OFILES) $(IFLAGS) $(LFLAGS) $(LIBS)
    @echo  "  make finished at `date`"

%.o: %.C
   $(CCC) $(COMPILEFLAGS) $(IFLAGS) -c $<

%.C: %.H

tidy:
    $(RM) $(OFILES)
    $(RM) *.*~ \#*\#

clean: tidy
    $(RM) -rf Templates.DB/Modules.DB
    $(RM) $(EXECUTABLE) Makefile.bak core Templates.DB/*
    $(RM) ir.out mon.out core $(EXECUTABLE).errs .make.state

depend:
    $(MAKEDEP) -- $(CFLAGS) $(IFLAGS) -- $(OBJECTS:%=%.C)

# DO NOT DELETE -- MAKEDEPEND needs this line. dude.


home Questions? Comments? Arguments about C++? Mail the CS032 TAs.

Last modified: Thu Sep 3 03:20:09 EDT 1998