Proper C/C++ Style

Introduction and Weltanschauung. A program is not simply a set of instructions to the compiler. It must also explain clearly and succinctly to others its purpose and internal workings. Think of a program as a piece of technical literature meant to be understood by other human beings as well as by the compiler. In the interests of clarity, I ask that you develop certain programming habits early. These could save you pink slips and hours of pointless debugging later. Develop a programming style that is both efficient and clear. In the long run, the costly part of maintaining software is not memory or hardware, but programmer overhead at $100 or more per hour.

This guide is meant to help you oraganize a small program.

The Global Structure of Programs in C/C++. The global form of every program is as follows. The program should have a comment box at the beginning. Then insert your preprocessor directives, that is, your #include statements. Follow this by any global variables and constants and then your function prototypes.

/*****************************************************

    < name of program >(.c|.cpp)
    
    by < your name >

    < xx Month yyyy >

    Place a brief statement here explaining the purpose
    of your program.  Just a couple of lines will do.  Explain
    how to properly run the program.

******************************************************/

    #include<iostream.h>  //Place library includes here.
    #include<math.h>

    int junkGlobalVariable;  //global variable
    const double junkConstant = 2.34343;  //global constant

    //Next, put your function prototypes (for one-file programs)
    //You may choose to place these in a separate header file and 
    //include them with a #include statement

    int main(void)
    {
        int radiusOfTire;  //This is the camelback style for variable names
        char is_tire_flat;  //This is the underscore style.  Both are acceptable.

        //Give your variables names that describe their role in your program.
        //Single letter variables are OK for loop counters as in this example.

        for(int k = 0; k < 50; k++)
        {
            cout << k << "\t" << k*k << endl;  
        }
        return 0;
    }

Variables and Variable Names. Remember to avoid the 48 C++ keywords as variable names. Here are all of them, listed for your reference

auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while

These turn color when typed into vi or any modern IDE. Avoid beginning variable names with an underscore (_). We maintain a notational convention that uses the underscore for a specific purpose. Certain system variable names living in the libraries begin with this character and any bugs you generate this way will be very painful to isolate and correct. Do not begin variable names with a number or the compiler will give you an error message. Examples of acceptable variable names include the following.

    int x;
    char firstInitial;
    double numberOfIntervals;
    float slope2;
    char last_initial;

Here are some examples of bad variable names and why they are no good.

    int 2x;                  //This variable name starts with a number; you will get a compiler error.
    char _interval_size;      //Only use the underscore for specific purposes.  
    double new;              //The name new is a C++ keyword.  You will get a mysterious error message.

Give variable names that describe variable's role; this makes your program self-documenting. For instance, if you have a variable holding a street number, you might want to call it ... streetNumber. It is a common convention to start all variables and functions with a lower case letter and separate words with underscores or caps as in the two examples below.

    int numberOfChildren;          //camel notation, separate by caps
    int number_of_children;      //separate by underscores

These example shows picturesque variable names that make the purpose of the variable clear. For loop counters, you may use one-letter variable names, if the variable is declared inside the loop as in this example. The purpose of a variable declared inside a loop like this is quite clear to the reader.

    for(int k = 0; k < 50; k++)
    {
        cout << k << "\t" << k*k << endl;  
    }

Indentation. A program with no indentation is an indeciperable mess. Proper indentation is essential to easy program maintenance. Each function body, including that attached to main, is contained inside a pair of matching braces which sit at the left-hand margin as shown below. Too many levels of indentation provide a reminder that you are not modularizing your code properly. Here we show how the main routine should be indented.

    int main(void)
    {
        ...
        return 0;
    }

The body of a process control statement such as an if, while, do while, or for should be placed between two matching braces; all code between these two braces should be indented one further tab stop in than the braces. Here we show an example with three levels of indentatation

    int main(void)
    {
        int numberOfDays = 5;
        if(numberOfDays <= 5)
        {
           for(int k = 1; k < 40; k++)
           {
               numberOfDays++;
           }  //end of for loop
        }
        return 0;
    } 

With this style of indentation, it is easy to see how the braces match. This will save you frustration and annoyance sorting out the beginnings and ends of nested loops. You may write one-line bodies of process control statements as follows.

for(int k = 1; k < 40; k++)
    numberOfDays++;

However, the style in the example before this one is nicer because you may add lines to the body of the for loop quickly and easily. It is a common mistake to upgrade from a one-liner loop body and to forget to include curly braces. This will cause the loop to iterate on the first line of code and perform the rest when it ends. To avoid making this mistake it is better to do this.

    for(int k = 1; k < 40; k++)
    {
        numberOfDays++;
    }

If your loops nest, place helpful comments as was done in the example above that help a future reviser of your code to figure out where the various loops end. If you are nesting a large number of levels, it's time to think about doing a little modularization into functions.

Comments. Place comments in your code that help a reader figure out easily what you are doing. By making variable names self-explanatory, you will limit the need for comments at variable declarations. Place a comment at the end of a loop if it is nested to help the reader unsnarl the loops. At the beginning of each function implementation, place a comment telling the purpose of the function. Give functions picturesque names that depict their role in the program. Here is the style of a typical function.

/*****************************************************

            absoluteValue

    precondition: x is a double
    postcondition: x returns the absolute value of x

******************************************************/

double absoluteValue(double x)
{
    if( x <= 0 )
    {
        x = -x;
    }
    return x;
}

Notice how we have avoided the stylistically excrescent multiple return. Here is how our implementation would look with a multiple return.

double absoluteValue(double x)
{
    if( x <= 0 )
    {
        return -x;  //one return statement
    }
    else
    {
        return x;  //the second return statement hmmmmmm......
    }
} 

Multiple returns can cause elusive bugs that absorb many hours of frustrating debugging. They cause your function to have several exit points and this can cause difficulty and confusion. Avoid them whenever possible. It is best to have a single point of exit from any routine.

White Space. This magical invisible stuff can make your programs easier and more enjoyable to read. It also makes the program clearer to you when your head begins to cloud over from hours of debugging and making one stupid mistake after another. Skip lines between major pieces in a program to set them off.

Use an extra space around binary operators. For example

   y=x+y;

looks crowded. This example here is better.

   y = x + y;

Surround binary operators like == and <= with a blank space. Compare if(x==0) to if(x == 0). A little white space makes your program prettier to look at and easier to read.

goto. We eschew the excrable goto statement at all times. If you cannot avoid using the goto statement, your program has a significant stylistic or design flaw. Reëvaluate the design of your code in these circumstances. Note well that we do not countenance the felonious use of this unstructured feature of C++ by contaminating our classrooms with it. Avoid it like the plague. It can cause bugs that you may never be able to unravel, and it makes understanding your program a frustrating chore to others.

Later when you learn about exception handling, you will see that you can get unwedged from deeply nested situations and call stacks without the aid of the dreaded goto. Good exception handling makes your code less cluttered and more straighforward.

Preprocessor Directives. When using constants in a program, the C++ const modifier is the preferred vehicle. The declaration

   const double PI = 3.1415926;

is far preferable to the preprocessor directive

   #define PI 3.1415926

The preprocessor is not a part of the C++ language. It merely goes through your program prior to compile time, substituting 3.1415926 for the token PI. Because of the way the preprocessor works, constants passed to a function as parameters are not checked for proper type. This was a constant source of bugs in the C language. Also, constants can be declared locally and have the scope of the block in which they are declared. Mathematical constants and constants used thoughout your program should probably be global.

In fact, you should make every attempt to avoid global variables, unless they are constant quantities. Keeping variable scopes small cuts down on the coupling between routines, and avoids namespace collisions.

The C++ language also offers a feature that obviates the preprocessor macro. For instance, in the interests of efficiency, you might insert the macro

    #define cube(x) ((x)*(x)*(x))

These macros cause the compiler to do a literal substitution. In the example here, if the compiler sees

cube(5)

it replaces the expression cube(5) with 5*5*5. No type checking is done, so bugs caused by these macros are very difficult to find and remove. This replacement is done prior to compile time as a search-and-replace operation by the preprocessor. If you create a macro with a mistake, it can trigger mysterious error messages that will vex and frustrate you.

Instead, the C++ language provides inline functions. Instead of using a macro for cube, we can write

    inline double cube(double x) 
    {
        return x*x*x;
    }

This makes a suggestion to the compiler that it should substitute in function code and eliminiate the function call. Before this happens, proper type checking occurs, and the compiler can flag a type error for you. Calls to an inline function are less costly than those of ordinary functions. You should only inline short functions. The compiler can elect to ignore your inline request. Sophisticated optimizing compilers inline short functions for you. In any event, the preprocessor macro is an antiquated construct in the ordinary run of C++ programming.

Musings on Modularization. The key to writing quality programs is proper modularization. You should break your program into functions that do all the real work. The job of main should be to orchestrate the work of the various functions.

The code for each new function can be written into a separate program, tested, debugged, and then be added into your project. Functions bodies should seldom be more than a page long. Each function should have one purpose. If your parameter list has a large number of elements or your function is struggling to juggle several jobs at once, this is an indication your function may be too complex. In these cases, break the offending function down into smaller, more manageable pieces. Functions that do a specific job well can be reused readily.

Remember the words of General George S. Patton: "I don't pay for the same real estate twice." You are encouraged to develop your own personal libraries of useful functions.

Assorted Antiobfuscatory Measures. Hacked code that conceals the meaning of your program is not good programming practice. Just as a minor example, consider the loop shown below.

    int k= 0;
    while(++k < 10)
    {
        doAssortedJunk(k);  
    }

is far less straightforward than

    int k= 0;
    while(k <= 10)
        {
            doAssortedJunk(k);
            k++;  
        }

Avoid "clever" constructions that try to do too many things at once. These make your programs more difficult to debug and maintain.