Adhering to a good programming style will help promote good readable
code and will eliminate most of the common programming mistakes.
Every student will be expected to follow this guide.
This section is going to cover some general styles. This will include directory hierarchy and file naming. Creating a common hierarchy will keep you from having to search for where your headers files and source files are located on your machine. Keeping to a good naming convention for your files will help in cross platform development where you compile your application on different operating systems and will help you know by looking at the name of the file what the file pertains to.
Source Files
Source files will be in all lower case separated by underscores. All C++ files that you create will keep to a .cpp extension and all header files you create will keep to a .h extension. Every .cpp file will have an associated .h file. The .h file and the .cpp file will have the same names but different extensions. For example if you had some functions that implemented manipulating a student database then the file name will be descriptive.
SourceFileName : student_database.cpp
IncludeFileName: student_database.h
Keeping the source and include file the same name and only differing
by an extension will keep you from searching for which include file is
associated with this source file.
For each project or homework assignment that involves some programming there should be a separate but common named directory structure. All include files for a given project should go into an include directory and all source files .cpp should go into a source directory. These two directories are typically sub directories of the project directory. When compiling your software you do not want to flood the source directory with object files generated by the compiler. Object files are typically identified with a .obj extension on the PC side and .o extension on the unix flavor side. So an obj directory should also be created. The exe file should be sent to an exe sub directory. The files that actually compile the software we are writing will also go into its own directory. Since the code we are developing is cross platform then the files that will build the software will be in a sub directory called compile. Now within this compile directory I like to create another sub directory depending on which compiler is being used to build the files. For example if I am using borland then the full sub directory name will be
compile\borland
Example: Our project is to build and create a simple database to manipulate student information. Let's say that to implement this application we only need one source file and one header file:
students_database.cpp
students_database.h
What would the directory structure be like? The first name is up to you but I would create a descriptive name:
projects/student_database
Notice how I keep to an all lower case naming convention. This directory will be what we call the root directory. Where do we create the source file students_database.cpp?
projects/student_database/src/student_database.cpp
and the header file
projects/student_database/include/student_database.cpp
and object files and exe files will go into
projects/student_database/obj
projects/student_database/exe
and the ide files or makefiles or any other files will go into the following sub directory depending on how the files are being built.
projects/student_database/compile/borland
projects/student_database/compile/unix
projects/student_database/compile/microsoft
If you are assigned another project like 3d-animation then the directory structure would be
projects/3danimation/src
projects/3danimation/include
projects/3danimation/obj
projects/3danimation/exe
projects/3danimation/compile/borland
projects/danimation/compile/unix
projects/3danimation/compile/microsoft
Note that in the above examples I am assuming that the target type is an executable. The directory
projects/3danimation/exe
projects/student_database/exe
may be executables but you may also have static libraries(.lib on PC and .a on unix) and shared libraries(.dll on PC and .so on Unix). In that case you will need to develop names for those targets.
projects/student_database/shared_lib
projects/student_database/static_lib
All include files will have the following #ifndef #endif wrapper to avoid multiple includes. If we were to create the student_database.h file it would look something like this
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// File Name:
student_database.h
//
// Author Name: Garrett Potts
//
// Last Revision: January 15, 1999
//
// Description: This file is used
to show how to document the header file and how to
//
show where to place the #ifndef #define and #endif. This file will
hold
//
all prototypes for manipulating a student database.
//
// Change Log: Created the
file and wrapped with #ifndef and #endif
//
// Portability:
Unix, Os/2, Windows 95/NT ...etc
//
// Copyright:
This file is free to use as long as the copyright information and the authors
//
name stays attached.
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef STUDENT_DATABASE_H
#define STUDENT_DATABASE_H
//all prototypes, enumerations, classes...etc will go in here.
#endif
Once this is done for the header file the student_database.cpp must be done in the same fashion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// File Name:
student_database.cpp
//
// Author Name: Garrett Potts
//
// Last Revision: January 15, 1999
//
// Description: Will hold the implementation
of all functions and classes found in the
//
student_database.h file.
//
// Change Log: Created the
file and added the #include statements where appropriate.
//
// Copyright:
This file is free to use as long as the copyright information and the
//
authors name stays attached.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "student_database.h"
//all functions and class implementations go here.
Eventually when we get to classes we will try to figure out a clever way of hiding main and figure out how to treat the main entry point as an application object. The first approach is goin to relevant only because we haven't studied classes yet.
In both cases main will be defined in its own filename called main.cpp
and the application entry will be in file application_main.cpp with associated
header file application_main.h. To allow for command line switching
we will always create main with all three arguments.
//file main.h
//include your headers here
#include "application_main.h"
#include "main.h"
int main(long argc, char *argv[], char *env[])
{
return ApplicationRun(argc, argv, env);
}
ApplicationRun function will be defined in the file application_main.cpp and will be prototyped in the file called application_main.h
application_main.h will look like this.
#ifndef APPLICATION_MAIN_H
#define APPLICATION_MAIN_H
int ApplicationRun(long argc, char *argv[], char *env[]);
#endif
Note the Name ApplicationRun can be more descriptive if you like.
For classes we will see how to perform the following.
//file main.h
//include your headers here
#include "application_main.h"
#include "main.h"
int main(long argc, char *argv[], char *env[])
{
Application.Run(argc, argv, env);
}
Where Application is the name of our main application object and run is the member function that is going to be called to control the application.
All variables will adhere to a good naming convention. A good
variable name will immediately be able to tell us the type and what it's
going to be used for. Example:
int main()
{
int x, y, z;
x = 0;
y = 100;
z = 100;
x = (y + z) / 2;
cout << "The average grade is: " << x << endl;
return 0;
}
Observing the names of x, y, z we have no clue what they pertain to. I didn't know until reading the cout statement that x was to hold an average integer grade. A better naming convention would have produced something like this:
int main()
{
int iAverageGrade, iGrade1,
iGrade2;
iGrade1 = 100;
iGrade2 = 100;
iAverageGrade = (iGrade1
+ iGrade2) / 2;
cout << "The average grade is: " << iAverageGrade << endl;
return 0;
}
The i pre-pending each variable name gives indication of the variable
type. In this case the variable type is integer. The following
table will be used by the class. The prefixes I will make optional
but are a good idea to help in identifying the type of the variable:
Variable Type | Variable Prefix Value |
int | i |
short | s |
long | l |
unsigned | u |
unsigned long | ul |
unsigned short | us |
float | f |
double | d |
long double | ld |
const | k |
short int | si |
long int | li |
Examine the following code fragment that applies the conventions in the table:
int main()
{
unsigned short
usMyFirstExample;
unsigned long
ulMySecondExample;
long
lMyThirdExample;
char
cMyFourthExample;
unsigned char
ucMyFifthExample;
const long
klMySixthExample = 0;
//replace this comment with the first line of code.
return 0;
}
Notice how the variable names are lined up with each other. I
will not accept code that looks like this:
int main()
{
unsigned short
usMyFirstExample;
unsigned long
ulMySecondExample;
long lMyThirdExample;
char cMyFourthExample;
unsigned char ucMyFifthExample;
//replace this comment with the first line of code.
return 0;
}
The variables are harder to see and read and there will be one blank line after the last variable declaration and coding will begin where the comment line is. The return will have a blank space before it. Constant expressions are an exception to the rule defined above. If any constant expression is defined It will have all capital letters separated by underscores for example code:
#include <iostream
using namespace std;
const long ARRAY_SIZE
= 100;
const char* INSTRUCTOR_NAME = "Garrett
Potts";
int main()
{
long lTestArray[ARRAY_SIZE];
long lTestArrayIndex = 0;
for(lTestArrayIndex = 0; lTestArrayIndex < ARRAY_SIZE;
lTestArrayIndex++)
{
lTestArray[lTestArrayIndex] = 0;
}
cout << INSTRUCTOR_NAME << endl;
return 0;
}
Enumerations are going to have a similar naming convention much like constant expressions defined at the end of the Variables section. observe the following code fragment:
//file my_first_header.h
#ifndef MY_FIRST_HEADER_H
#define MY_FIRST_HEADER_H
enum VirtualKey {
KEY_1 = '1'
,KEY_2 = '2'
,KEY_3 = '3'
,KEY_4 = '4'
,KEY_5 = '5'
};
#endif
Typically the above enumeration will be found in the header file.
Notice the format of the enumeration. The enum followed by the name
of the enumeration followed by the begin bracket. Each enumeration
is an integer constant. This means the only valid values are integer
based, in the above the char type is used and all chars are represented
as an integer. All enumerations that have multiple lines will have
the above format. The names are in all capitals and the = sign is
separated by blank space to the left and to the right of the equals.
Using the above in a program will look like this
#include <iostream>
#include "my_first_header.h"
int Run()
{
VirtualKey KeyTest;
KeyTest = VK_1;
return 0;
}
int main()
{
return Run();
}
Spacing on control structures will be discussed in another section.
Control structures will allow for conditional flow through the program.
We will adhere to a strict standard when implementing control structures.
if/else control structures will have the following style.
int main()
{
long lGrade1, lGrade2;
if(lGrade1 < lGrade2)
{
//code goes here
}
else if(lGrade1 == lGrade2)
{
//code goes here
}
else
{
//code goes here
}
}
The general rule is this for nested if statements
if( <expression )
{
if( <expression ) //first
nest
{
if( <expression ) //second nest
{
//code goes here
}
else if( <expression ) //else for the second nest and a third nested
if
{
//code goes here
}
else
{
//code goes here
}
}
else
{
//code goes here
}
}
Notice the indentation. All code will be indented for three to five spaces. This can typically be setup in any editor by using the tab key and setting the tab space to 3, 4 or 5 spaces. Also all if blocks will have the { } braces. I do not want to see code that looks like this
if( <expression )
//program statement
else
//program statement
Even if there is only one statement pertaining to the if or else condition
always use the {} braces as described above.
Switch cases only are used for Integer based values. When comparing
equality on a variable to more than one value it is more efficient to use
a switch/case than an if/else control structure. This will be explained
further in class. Assume I have a variable Called KeyCode and using
the enumerations defined in the enumeration
section for Key values we produce the following code fragment:
void ExampleSwitchCase(VirtualKey KeyCode)
{
switch(KeyCode)
{
case VK_1:
{
//program statements go here
break;
}
case VK_2:
{
//program statements go here
break;
}
case VK_3:
{
//program statements go here
break;
}
case VK_4:
case VK_5:
{
//program statements go here
break;
}
default:
{
//program statements go here
break;
}
}
}
All case statement will have a {} braces. This is a good coding practice since defining a variable within the case statement will produce warnings or errors depending on the compiler so using scope indicators {} will eliminate this problem. Also notice VK_4 and VK_5 are sequenced one after the other, this means that if KeyCode evaluates to VK_4 or VK_5 that code will be executed for it. Also all case statements will have a default case whether you need one or not. Finally all case statements will have a break. Forgetting the break is a common programming error.
The for loop is very flexible and because of this we will try to keep to a more strict syntax for program readability.
Syntax:
1. for(<section 1 ; <section 2; <section 3)
//a single statment
2. for(<section 1 ; <section 2; <section 3)
{
//any number of statements
}
In form 1 this leads to common programming errors by assuming later all you have to do is add another statement and the loop will apply to that statment as well. The begin end braces will make sure that this common error will not happen. Always use form 2. Form 2 is the desired form for constructing the for loop. This form will guarantee that when you add statements later it will also apply to the added statment since they
are enclosed in braces. Now lets break down each section in the for statment:
<section 1 : This section will be strictly reserved to initializing loop variable or variables. Or whatever variable you are using to test for loop termination.
<section 2: Will be reserved for testing the loop variable for a stopping condition. Make sure that the condition will be reached to avoid infinite loops.
<section 3: Will only be sued to adjust the loop variable to eventually reach the stopping condition.
Example:
for(Counter = 0; Counter
< 50; Counter++)
{
cout << "Counter = " << Counter << endl;
}
The semicolons separating each section is required. Notice I did not do this:
for(Counter = 0; Counter
< 50; Counter++, cout << "Counter = " << Counter <<
endl)
{
}
Even though the above is legal and should generate the same output as the first program it is hard to read. Only place in each section what is required to initialize test and adjust the loop variable, NOTHING ELSE.
Now since newer version of compilers are coming out the scope of variables defined in the initialization section of the for loop would be only within the for loop or within the body of the function in which the for loop is defined in. I believe the standard is to have the scope only pertain to the scope of the for.
Example:
for(long Counter = 0; Counter < 50; Counter++)
{
cout << "Counter = " << Counter << endl;
}
cout << Counter << endl; //for some compilers this is undefined
symbol
for(long Counter = 0; Counter < 50; Counter++)
{
cout << "Counter = " << Counter << endl;
}
Some compilers would treat the last statement as undefined symbol because
they assumed that the scope of the variable was only within the scope of
the for statement and once the for statement is exited then Counter no
long exists. The second for loop defines Counter again and for some
compilers this is ok if the scope of Counter for the first loop was only
for the loop and not the entire function. If the scope was for the
entire function then the Counter variable would be multiply defined.
To avoid this all together we will define our loop variables at the top
of the scope.
int main()
{
long Counter = 0;
//loop variable defined at top of scope
for(Counter = 0; Counter
< 50; Counter++)
{
cout << "Counter = " << Counter << endl;
}
cout << Counter
<< endl;
for(Counter = 0; Counter
< 50; Counter++)
{
cout << "Counter = " << Counter << endl;
}
return 0;
}
Now the above code has no problems with any compilers.
Indentation will be identical to the if/else when we next for loops
for(Row = 0; Row < 50;
Row++)
{
//statements
for(Column = 0; Column < 50; Column++)
{
//statements
}
//statements
}
the next can go as deep as you would like. In the above program
the inside loop indicated by Column will be executed Row number of times
which is 50 and the inside loop indicated by column will be executed Column
number of times which is also 50.
While loops will have the following coding style:
while( <expression )
{
}
The above will be used even if we have only one statement to execute. Nesting while loops will have the same format as for loops and nested if statements.
while( <expression )
{
while( <expression )
{
while( <expression )
{
//and so on
}
}
}
Make sure all braces match up and you use proper indentation.
do/while loops will abide by the following syntax
do
{
//statements or statement
} while( <expression );
I like to put the while on the same line as the last brace. This will lwt you know that it is part of a do/while and not just a while statment.
nested loops will be the same as the previous sections
do
{
do
{
} while ( <expression );
} while( <expression );
This section will pertain to coding standards when working with functions. In C++ we would like to keep the number of functions that exist in the program to a bare minimum or next to none. C++ should be about objects and communicating between them and avoid the functional approach as much as possible. This section will show you some coing standards that we will abide by in this course.
The length of the functions should allways fit inside an editor.
If your function stretches for more than 15 to 20 lines you probably are
doing a little too much and need to create some additional functions.
Functions are used to enhance readability and to make your programs more
modular and extendable. Make sure your function does what it
is designed to do and no more. For example if you were to implement
a square function it should not perform I/O and subtracions and divisions,
it should just square a value and return it.
All functions will be documented. At the top of each function you will have a comment that looks something like this:
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name:
Square
//
// description:
The function will take an argument and return its square.
//
// arguments:
// double Value
This is a double precision value and will be squared and
//
the result returned back to the caller.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
inline double Square(double Value)
{
return (Value * Value);
}
I will allow comments inside your function. These comments will not be long they will be short and concise.
istream& ScanInput(istream& InputStream)
{
char CurrentChar;
while( (CurrentChar = cin.get())!=EOF) //get cahracter
and test for EOF. Short and concise
{
// this comment will be
and example of maybe having one way way way
// too long. Just
think if I used comments this large after every couple of statments.
The actual code
// can become unreadable.
switch(CurrentChar)
{
// this comment will be
and example of maybe having one way way way
// too long. Just
think if I used comments this large after every couple of statments.
The actual code
// can become unreadable.
case '%': //handle hex character indicator. Short and concise comment
{
break;
}
// this comment will be
and example of maybe having one way way way
// too long. Just
think if I used comments this large after every couple of statments.
The actual code
// can become unreadable.
case '&': //handle name value separator. Short and concise
{
break;
}
// this comment will be
and example of maybe having one way way way
// too long. Just
think if I used comments this large after every couple of statments.
The actual code
// can become unreadable.
default:
{
}
}
}
return InputStream;
}
Notice how the larger comments that extend multiple lines clutter up the function. Too many comments can be bad. Make sure that if you comment within the function you use short and concise comments as seen to the right of the while statement and to the right of a couple of case statements as seen in bold face. Put the larger comments in the description section of the documentation template that goes above the function. Look at the same function with just the short and concise statements.
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name:
ScanInput
//
// description:
The function will take an input stream and parse it and.
//
look for special characters. These characters will be
//
converted to their decoded values. The % tells us that the
//
next two characters will correspond to a hex number. The
//
larger set of comments that we saw above can go here in the
//
description section. If you need to do detailed explanation
//
of a complicated or confusing step in you function and need
//
to have several lines to explain it then do it here
//
// arguments:
// istream& InputStream
//
This is a double precision value and will be squared and
//
the result returned back to the caller.
//
// Type Name
//
More comments here if the argument existed
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
istream& ScanInput(istream& InputStream)
{
char CurrentChar;
while( (CurrentChar = cin.get())!=EOF) //get cahracter
and test for EOF. Short and concise
{
switch(CurrentChar)
{
case '%': //handle hex character indicator. Short and concise comment
{
break;
}
case '&': //handle name value separator. Short and concise
{
break;
}
default://Pass through all other characters
{
}
}
}
return InputStream;
}
Coming up with a function name will adhere to the same rules as a variable name, where the first character of each word is capitalized.
void MyTestFunction()
{
}
Each word in the function is capitalized. Make the name descriptive
and concise. There is no need to create a 25 character function name.
Arguments to the function will consist of a name that is descriptive. Just by looking at the name of the function and the name of the arguments a user should know exactly what the function is going to do.
void Output(string Data)
{
}
versus
void O(string D)
{
}
Assume both function do the same think. With just looking at the name of the function and the argumets can you tell me which one has a better description? I hope you chose the first form, Output(string Data). The name of the function tells us it outputs something and the argument tells us it corresponds to some data string. For multiple arguments separate each with a comma and a space.
void DemostrationFunc(long Argument1, long Argument2, double Argument3)
{
}
and not like this
void DemostrationFunc(long Argument1,long Argument2,double Argument3)
{
}
In the second form the arguments are jumbled close together and are
hard to read use the first form when spacing out arguments.
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name:
class MyClass
//
// description:
Give a general description of what the class does.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
class MyClass
{
public:
:
:
:
:
:
:
:
:
:
I want all documentation of the member functions to be placed in the source file. The dcumentation will abide by the same rule as the function docs.
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name:
ProectedFunction
//
// description:
Performs a nice binary search.
//
// arguments:
// Type Name
//
More comments here if the argument existed
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
void MyClass::ProtectedFunction()
{
}