When it comes to learning how to program in C++, there's no substitute for experience. But there are some things that are good to keep in mind. Advice on how to construct programs can be found in the last 6 chapters of More C++ for Dummies and in chapters 23 and 24 of the 3rd edition of Stroustrup. Here is a brief summary of that advice:
IS_A
relationships. For example, a Manager IS_A
Employee. In a hierarchy of inherited classes, figure out
what properties the classes have in common, and make these
properties of the base class. This is called factoring.
Use membership in classes to represent
HAS_A
relationships, e.g., a Car HAS_A
Motor. So
make motor a member of the class Car.
Diagrams can also help illustrate the relationships
between different groups of classes.
A class is sufficient if it captures enough characteristics of the abstraction to permit efficient interaction between classes. For example, to be an effective representation of an employee, the class Employee must represent all the relevant properties of an employee in a way that's useful to the rest of the program. The program shouldn't rely on external functions or on outside data areas to be able to make use of the Employee class.
A class is primitive if you can't easily divide it into separate classes. A primitive class represents a single concept. Although dividing a nonprimitive class may increase the number of classes, it generates smaller, simpler, and more useful classes. Dividing up a primitive class results in subclasses that do not completely define a concept. As a result, the two classes must make frequent reference to each other. The interclass coupling rises dramatically.
Finally a class is complete if its public interface provides a full set of operations. Other classes can easily use a complete class because all the operations other classes will need are available.
.h
file, each
member function and data member should have a brief description describing
what it is for and, for a member function, what it does.
Description of member functions should explain what the
input arguments are, what the function does, and what it returns.
#define
statements.
Use underscores to delimit words in a multiword name.
Prefix Type ____________________________________________________________________ c char n integer b bool f float d double l long u unsigned p pointer s string sz ascii stringFor example,
int *pnMode
is a pointer to an integer.
sun1% g++ -g yourprogram.C sun1% gdb a.out(
sun1%
is the prompt on sun1.ps.uci.edu in the computer lab.)
Once you have debugged the program, compile it without the -g
flag; it will run faster that way.
There is also ups which is another debugging
package you can use. Look up the man pages for documentation.
You can also insert error checking code into your program while you
are developing it. Later, when things are running smoothly, you can
take the error checking lines out. For example, you can make an
isLegal() member function that checks the range and type
of each data member or the input parameters passed to a member function.
One can also ``throw and catch exceptions''.
-pg
flag. Then you run it as usual. This
produces a file gmon.out
. Then type gprof, and an
evaluation of your program will be displayed. So the commands you
type look like
sun1% g++ -pg myprogram.cc sun1% a.out sun1% gprofYou should estimate how many times the innermost loop is evaluated when your program is run. You may find that it's millions of times. So in the innermost loop you should have very low level code that's easy and quick to execute; not high level code that involves a lot of manipulations. For example, you don't want if statements in the innermost loop if you can help it. You also don't want code in the innermost loop that costs a lot of overhead, e.g., going through a lot of pointers to get to the right place in memory. Efficiently accessing (RAM) memory is important. Sometimes you just have to experiment to see what runs faster on a particular machine with a particular compiler. Different compilers optimize code in different ways for different processors. The order in which you do operations can make all the difference. For example, if you go through the elements of a matrix as in matrix multiplication, you want the fastest moving index in the innermost loop. C and C++ store elements by row, so when going through matrix elements, the column index moves the fastest. But Fortran stores matrix elements by column, so the row index moves the fastest. Accessing adjacent elements in memory is better. The less jumping around in memory, the faster the program. As you go through a row in C or C++, you are going through elements that are stored next to each other. Suppose we multiply matrix A by matrix B to get matrix D. We take a dot product of a row in A with a column of B. lec12matrix.eps So we can efficiently access the elements of A but not of B. This slows down our program. To make things more efficient, we can transpose matrix B. Let's call the transposed matrix tB. Now matrix multiplication involves the dot product of a row of A and a row of tB. In both cases the elements are stored next to each other. Of course it takes a little work to transpose B, but it is worth it if the matrices are big.
Another reason for a program to run slow is if the executable becomes larger than the available RAM (Random Access Memory). If this is the case, the executable has to be swapped in and out of memory, and this slows things down. So make sure you're program isn't too big. Efficient use of memory is one of the benefits of C++.