C++ Primer

A prerequisite for the 3-day C++ design and programming course, or for the 5-day analysis, design and programming course is that participants know some very simple basics of C++ syntax. This document covers most of what you'll probably find useful to know.

The material starts off with some Linux-based course issues. If your course is going to be a Windows-based course (which makes no difference whatsoever to the C++ itself), then you can go straight to the C++ section, after the first couple of paragraphs.

Organisation

If you are used to Fortran or Algol then the first surprise when using C++ is the use of many files to hold your code. Related, commonly used definitions (and occasionally definitions--don't worry about the distinction for now) are put into "header" files. Each class is usually defined in a separate pair of files. A class' header file, with a .h filename extension, holds the definition--the data members and member function declarations--whereas the actual member function code goes in a file with typically a .C or .cpp or .cxx extension.

As the exercises progress, we will be copying previous exercises to new directories and adding files for each new class.

Compiling a large number of files would require developers to have an incredible memory and knowledge of the code structure, so a makefile is used instead. A makefile records the various dependencies that the files have on each other, and other gruesome details such as compiler switches. Writing makefiles is an arcane and unpleasant task. If you have a coding environment that does it for you (Visual C++ for example), you can be very grateful. If you can find someone who likes writing makefiles, don't have them put under medical supervision, get friendly with them and get them to write your makefiles. For the course, just hope that the lecturer brings the makefiles with them, and that they work.

If we are using a "DIY" coding environment, in early exercises we will probably use the compiler and linker on individual files, like this

g++ -o test somefile.cxx

or

egcs -o test somefile.cxx

in later exercises we will just execute

make

Layout

A C++ program, like a C program, must have a distinguished routine called main . This is where execution will begin. It could look like this:

int main() {

    beam.boilKettle();

}

All routines have a declared return even it's only "a value from the empty set void ", i.e. not really returning anything at all. (You should think of "void" in the sense of "the great void", rather than "invalid". The main() routine returns a number to the operating system, although, uniquely for main() the return statement itself is optional. With an explicit return, our main() looks like this:

int main() {

    beam.boilKettle();

return 42; // There is a constant we could use: return EXIT_SUCCESS;

}

Typically a routine might well have something to return:

float calculateLuminosity() {

    return transducer.voltage() * spec.calibrationConstant();

}

Routines other than main have developer-chosen identifiers like calculateLuminosity . There are two (well three) styles for identifiers. You'll see I'm using meaningful names with capitalisation. Some use meaningful names with underscores. Some don't use meaningful names. We hope we will never have to read code written by the last group.

The next thing you'll note is the parameter list between the parentheses following the routine identifier. Examples so far have not taken any arguments . Here is a routine that does take arguments

float integrate(float start, float end) {

    // etc. This, by the way is a comment

}

This routine is being declared to take two floating point number arguments (and, as we've seen, is being declared as returning a floating-point argument as well). There are several built in types including floating-point types. They are all primitive ; that is they are not object-oriented types. The built-in types are variations on bool , char , int and float . These are not very powerful, or very friendly, and we will spend most of our time implementing our own types-- classes --or getting classes from libraries. This is the object orientation. Everything is done by objects; objects are built from classes; our development work is therefore designing classes. (Additionally we have to design the type system . Demonstrating the subtle connections and differences between the class system and the type system is one of the goals of the course.)

Getting back to our routine, we next come to the code block , set between curly brackets (braces). (These curly brackets are like the begin and end of Pascal.) A code block gets executed; it's what does the work. A code block can be treated as a single statement , i.e. where the grammar says give me a statement, a code block can be given (another term for a code block is a "compound statement"). A block is also a scope region . Identifiers declared between the curly brackets of a block are only visible--in scope--there, to that code, or to any scopes nested within that scope (provided they come after the declaration). They can't be referenced from other, non-nesting, code blocks. This is to give us modular code of course; to limit the impact of changes.

Be careful reading curly brackets they are used in at least two other ways (class member lists and array initialization).

In a code block, we can have control, expression or declaration statements. Expression statements probably look pretty familiar and end in a semi-colon. For example:

tempResult = foo + bar - numberFirstThoughtOf;

A very important but probably less familiar expression is the message send , such as:

transducer.voltage()

The dot is the message send operator . (Technically it's called the member select operator, but if you follow the rules of righteous object-orientation, it is effectively the message send operator.) On the left is the destination object's variable and on the right is the signature of what we want it to do, i.e. we expect the object transducer to find a routine resembling voltage() in its class definition.

A declaration statement, as opposed to an expression statement, perhaps the declaration of the variable holding the abovementioned object , would look like this:

Measurer transducer;

We give a type Measurer (a class type in this case) followed by a space and then an identifier transducer. The line is again terminated with a semi-colon. The declaration of a variable of primitive type would look like this:

float rydbergH;

All variables must be declared and defined before they can be used. Beware that there are no default initial values for local primitive variables (or data members).

Variables (and a few other things) can be declared const , meaning the obvious-- read-only .

const float RYDBERG_H(1.097e7); // the capitals are just convention for constants

We also see here the initialisation of variable as it's declared and defined. You will also see this written
    const float RYDBERG_H = 1.097e7 ,

but the equals sign is already confusing enough, and there are good reasons for getting used to this improved form.

All routines also have to be declared before they can be used. Apart from main , we will be being thoroughly object oriented and declaring our routines in classes. (I.e. whilst the compiler's run-time executive will run main , all application functionality will be run by objects of classes. Explaining and justifying that sentence is what much of the course is about.)

The remaining statements are the control statements . We've seen one-- return .

There is also the if statement and the if else statement.

if ( universeExpandingForEver ) {

    // statement(s)

}

 

if ( universeExpandingForEver ) {

    // statement(s)

} else {

    // other statement(s)

}

Notice that there is no then or endif or fi . And notice that we again have parentheses, but for an entirely different purpose. In fact parentheses serve many purposes: holding argument lists, holding control expressions, altering precedence in expressions, etc. (Those of you who know the language will know that the curly brackets there are not necessary if there's only one statement. However, always using curly brackets is an important safety/style guideline.)

The while loop statements

while ( universeExpanding ) {

    // other statement(s)

}

 

do {

    // other statement(s)

} while ( universeExpanding )

And there's also the for statement. It's fairly complex, and not at all like a FORTRAN or BASIC for/next. We'll just look at the most typical kind of for.

for ( int i = 0; i < someBoundary; ++i ) {

    // other statement(s)

}

The first part of the control is executed just once, at the outset; here, it is declaring, defining and initialising a loop counter to zero. The second part of the control is exactly like the control of a while; it is checked before each iteration, and the iteration only proceeds if it evaluates true ; here, we are using it to look for the loop termination condition. The final part is executed after each iteration; here, we use it to increment the loop counter

The style of indenting and layout used above is probably the commonest. There are others.

Operators

You have seen lots of operators already. You may have noticed that assignment is = (very common, even if mathematically suspect). The arithmetic operators and most comparison operators are pretty obvious. The non-obvious comparison operators are == for equal and != for not-equal.

The logical boolean operators are && for and and || for or. Not is ! .

Unusual to this family of languages are the compound assignment operators. They make some expressions easier to read, once you get used to them. a += b means a = a + b . Call it the add and assign operator, then you'll remember which way round it goes. There are corresponding compound operators for most binary arithmetic operators.

And the ++ operator in the for statement earlier? It's the increment operator. ++i means i += 1 , or i = i + 1 .

You can also write i++ . What's the difference? In x = y * ++i , i is incremented before it participates in the greater expression. With x = y * i++ , i is incremented after it participates. There are corresponding decrement operators -- .

Here's a table of the operators for reference, in order of precedence, highest first:

 

Finally a preview of what a class definition looks like.

class CalorimeterHit {

public:

    Position where();

    double energy();

private:

    double x;

    double y;

    double z;

    double e;

}

This class is called CalorimeterHit. Its objects can understand the messages energy() and where(). These messages can potentially be sent from any other object. The return from the energy() message is a double precision floating point number, and the return from the where() message is a Position object. Objects of the class secretly maintain their state in some rectangular coordinate variables and an energy variable.

The actual code isn't here. This is the header . This is what you'd find in the .h file. This is what users of your class would need to include (with #include , a pre-processor directive) in their programs to properly declare your class in order that they could use it.

Notice another, and different use of curly brackets. These enclose a list; a list of class members . They, like a code block, are also a scope region-- class scope . The C++ term for the a routine in a class is a member function (the general OO term would be method); and for the variable a data member (the general OO term would be instance variable, because each instance of the class will have its own personal variable).

The code would be in a companion .cxx file (or .cpp file on some platforms) and might look like this.

double CalorimeterHit::energy() {

    return e;

}

This looks pretty much like the routines we've already seen, other than the use of CalorimeterHit:: which is a necessary scoping statement. As the code is not in the class' curly bracket scope region, the routine has to be put specifically in the right scope.

You might be thinking, "What's the point of having a private variable e and a member function that just returns it?" Well the thing is that whilst creating software is moderately difficult, maintaining it over a long and happy lifetime is the real problem. Delegating the holding of hit energy to hit objects and hiding how they store and report it today , means that if we have a add a calibration correction tomorrow, the objects' clients will not see any change and will not themselves need to change.

Well that's it. If you didn't find something you were looking, that should have been included in a ready reference, let us know.

 

[ Briefings Home Page | Books by John Deacon | JDL Home Page ]