Day 21
Whats Next
Congratulations! You are nearly done with a full
three-week intensive introduction to C++. By now you should have a solid
understanding of C++, but in modern programming there is always more to learn.
This chapter will fill in some missing details and then set the course for
continued study.
Today you will learn
What the
standard libraries are.
How to
manipulate individual bits and use them as flags.
What the next steps are in learning to use C++ effectively.
The Standard Libraries
Every implementation of C++
includes the standard libraries, and most include additional libraries as
well. Libraries are sets of functions that can be linked into your code.
You've already used a number of standard library functions and classes, most
notably from the iostreams library.
To use a library, you typically include a header file in your source
code, much as you did by writing #include
<iostream.h> in many of the examples in this
book. The angle brackets around the
filename
are a signal to the compiler to look in the directory where you keep the header
files for your compiler's standard libraries.
There are dozens of libraries, covering everything from file
manipulation to setting the date and time to math functions. Today I will
review just a few of the most popular functions and classes in the standard
library that have not yet been covered in this book.
String
The most
popular library is almost certainly the string library, with perhaps the
function strlen() called
most often. strlen() returns
the length of a null-terminated string. Listing 21.1 illustrates its
use.
Listing 21.1. strlen().
#include
<iostream.h>
#include
<string.h>
int
main()
{
char
buffer80];
do
{
9: cout
<< "Enter a string up to 80 characters: ";
10: cin.getline(buffer,80);
11: cout << "Your string is " <<
strlen(buffer); 12: cout << " characters long." << endl;
}while
(strlen(buffer));
cout
<< "\nDone." << endl;
return
0;
}
Output: Enter a string up to 80 characters: This sentence has 31
characters
Your string is 31
characters long.
Enter a string up to 80 characters: This sentence no verb Your
string is 21 characters long.
Enter a string up to 80 characters: Your string is 0 characters
long.
Done.
Analysis: On line 6, a character buffer is created, and on line 9 the
user is prompted to enter a string. As long as the user
enters a string, the length of the string is reported on line 11.
Note the test in the do...while()
statement: while (strlen(buffer)). Since strlen() will
return 0 when the buffer is empty, and
since 0 evaluates FALSE, this while loop
will continue
as long
as there are any characters in the buffer.
The
second most popular function in string.h probably was strcpy(), which
copied one string to another. This may now be diminished somewhat as C-style
null-terminated strings have become less important in C++; typically, string
manipulation is done from within a vendor-supplied or user-
written string class.
Nonetheless, your string class
must support an assignment operator and a copy constructor, and often these are
implemented using strcpy(), as
illustrated in Listing 21.2.
Listing 21.2. Using strcpy.
#include
<iostream.h>
#include
<string.h>
int
main()
{
char
stringOne80];
char
stringTwo80];
stringOne0]='\0';
stringTwo0]='\0';
cout
<< "String One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "Enter a string: ";
cin.getline(stringOne,80);
cout
<< "\nString One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "copying..." << endl;
strcpy(stringTwo,stringOne);
cout
<< "\nString One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "\nDone " << endl;
return
0;
}
Output: String One:
String Two:
Enter a string: Test of
strcpy()
String
One: Test
of strcpy()
String Two:
String
One: Test
of strcpy()
String
Two: Test
of strcpy()
Done
Analysis: Two C-style
null-terminated strings are declared on lines 6 and 7. They are initialized to
empty on lines 9 and 10, and their values are printed on lines 12 and
13. The user is prompted to enter a string, and the result is put in stringOne; the two strings are printed again, and only stringOne has the input. Strcpy() is then
called, and stringOne is
copied into stringTwo.
Note that the syntax of strcpy() can be
read as "copy into the first parameter the string in the second
parameter." What happens if the target string (stringTwo) is too small to hold the copied
string?
This problem and its solution are illustrated in Listing 21.3.
Listing 21.3. Using strncpy().
#include
<iostream.h>
#include
<string.h>
int
main()
{
char
stringOne[80];
char
stringTwo[10];
char
stringThree[80];
stringOne[0]='\0';
stringTwo[0]='\0';
stringThree[0]='\0';
cout
<< "String One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "String Three: " << stringThree << endl;
cout
<< "Enter a long string: ";
cin.getline(stringOne,80);
strcpy(stringThree,stringOne);
// strcpy(stringTwo,stringOne);
cout
<< "\nString One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "String Three: " << stringThree << endl;
27: strncpy(stringTwo,stringOne,9);
28:
cout
<< "String Two: " << stringTwo << endl;
cout
<< "String Three: " << stringThree << endl;
33: stringTwo[9]='\0';
34:
cout
<< "\nString One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
cout
<< "String Three: " << stringThree << endl;
cout
<< "\nDone." << endl;
return
0;
}
Output: String One:
String Two:
String Three:
Enter a long string:
Now is the time for all...
String One: Now is the
time for all...
String Two:
String Three: Now is
the time for all...
String One: Now is the
time for all...
String Two: Now is
th_+||
String Three: Now is
the time for all...
String One: Now is the
time for all...
String Two: Now is th
String Three: Now is
the time for all...
Done.
Analysis: On lines 6, 7, and 8, three string buffers are declared.
Note that stringTwo is declared to be only 10
characters, while the others are 80. All three are initialized to zero length
on lines 10 to 12 and are printed on lines 14 to 16.
The user is prompted to enter a string, and that string is copied to
string three on line 20. Line 21 is commented out; copying this long string to stringTwo caused a crash on my computer because it
wrote
into memory that was critical to the program.
The standard function strcpy() starts
copying at the address pointed to by the first parameter (the array name), and
it copies the entire string without ensuring that you've allocated room for it!
The standard library offers a second, safer function, strncpy(), which copies only a specified number of characters to the target
string. The n in the middle of the function
name strncpy()
stands
for number. This is a convention used throughout the standard libraries.
On line 27, the first nine characters of stringOne are copied to stringTwo and the
result is printed. Because strncpy() does not
put a null at the end of the copied string, the result is not what was
intended. Note that strcpy() does
null-terminate the copied string, but strncpy() does not,
just to
keep life interesting.
The null is added on line 33, and
the strings are then printed a final time.
strcat() and strncat()
Related to strcpy() and strncpy() are the
standard functions strcat() and strncat().
The former concatenates one string to another; that is, it appends the
string it takes as its second parameter to the end of the string it takes as
its first parameter. strncat(), as you
might expect,
appends the first n
characters of one string to the other. Listing 21.4 illustrates their use.
Listing 21.4. Using strcat() and strncat().
#include
<iostream.h>
#include
<string.h>
int
main()
{
char
stringOne[255];
char
stringTwo[255];
stringOne[0]='\0';
stringTwo[0]='\0';
cout
<< "Enter a string: ";
cin.getline(stringOne,80);
cout
<< "Enter a second string: ";
cin.getline(stringTwo,80);
18:
cout
<< "String One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
strcat(stringOne,"
");
strncat(stringOne,stringTwo,10);
cout
<< "String One: " << stringOne << endl;
cout
<< "String Two: " << stringTwo << endl;
return
0;
}
Enter a second string: for spacious skies for amber waves of
grain String One: Oh beautiful
String Two: for spacious skies for amber waves of grain String
One: Oh beautiful for spacio
String Two: for
spacious skies for amber waves of grain
Analysis: On lines 7 and 8, two character arrays are created, and the
user is prompted for two strings, which are put into
the two arrays.
A space is appended to stringOne on line
22, and on line 23, the first ten characters of stringTwo are appended to stringOne. The
result is printed on lines 25 and 26.
Other String Functions
The string library provides a number of other string functions, including
those used to find occurrences of various characters or "tokens"
within a string. If you need to find a comma or a particular word as it occurs
in a string, look to the string library to see whether the function you need
already exists.
Time and Date
The time library provides a number of functions for obtaining a close
approximation of the current time and date, and for comparing times and dates
to one another.
The center of this library is a structure, tm, which consists of nine integer values for the second, minute, hour,
day of the month, number of the month (where January=0), the number of years
since 1900, the day (where Sunday=0), the day of the year (0-365), and a
Boolean value establishing whether daylight saving time is in effect. (This last
may not be supported on some systems.)
Most time functions expect a variable of type time_t or a pointer to a variable of this type. There are conversion routines
to turn such a variable into a tm data structure.
The standard library supplies the function time(), which takes a pointer to a time_t variable and fills it with the current time. It also provides ctime(), which takes the time_t variable
filled by time() and
returns an ASCII string that can be used for printing. If you need more control
over the output, however, you can pass the
time_t variable
to local_time(), which
will return a pointer
to a tm
structure. Listing 21.5 illustrates these various time functions.
Listing 21.5. Using ctime().
#include
<time.h>
#include
<iostream.h>
{
time_t
currentTime;
//
get and print the current time
time
(¤tTime); // fill now with the current time
cout
<< "It is now " << ctime(¤tTime) << endl;
12: struct
tm * ptm= localtime(¤tTime);
13:
cout
<< "Today is " << ((ptm->tm_mon)+1) <<
"/";
cout
<< ptm->tm_mday << "/";
cout
<< ptm->tm_year << endl;
17:
cout
<< "\nDone.";
return
0;
}
Output: It is now Mon
Mar 31 13:50:10 1997
Today is 3/31/97
Done.
Analysis: On line 6, CurrentTime is declared to be a variable of type time_t. The address of this variable is passed to the standard time library function time(), and the variable currentTime is set to the
current date and time. The address of this variable is then passed to ctime(), which
returns an ASCII string that is in turn passed to the cout statement on line 12.The address of currentTime is then passed to the standard time library function
localtime(), and a pointer to
a tm structure is returned, which is
used to initialize the local variable ptm. The
member data of this structure is then accessed to print the current month, day
of the month, and year.
stdlib
stdlib
is something of a miscellaneous collection of
functions that did not fit into the other libraries.
It includes simple integer math functions, sorting functions (including qsort(), one of the fastest sorts available), and text conversions for moving
from ASCII text to integers, long, float, and so forth.
The functions in stdlib you are
likely to use most often include atoi(), itoa(), and the family of related functions. atoi() provides ASCII to integer conversion. atoi() takes a single argument: a pointer to a constant character string. It
returns an integer (as you might expect). Listing 21.6 illustrates its use.
Listing 21.6. Using atoi() and related functions.
#include
<iostream.h>
int
main()
{
char
buffer[80];
cout
<< "Enter a number: ";
cin
>> buffer;
9:
int
number;
//
number = buffer; compile error
number
= atoi(buffer);
cout
<< "Here's the number: " << number << endl;
//
int sum = buffer + 5;
int
sum = atoi(buffer) + 5;
cout
<< "Here's sum: " << sum << endl;
return
0;
}
Output: Enter a number:
9
Here's the number: 9
Here's sum: 14
Analysis: On line 6 of this
simple program, an 80-character buffer is allocated, and on line 7 the user
is prompted for a number. The
input is taken as text and written into the buffer.
On line 10, an int variable, number, is declared, and on line 11 the program attempts to assign the
contents of the buffer to the int variable. This generates a
compile-time error and is commented out.
On line 12, the problem is solved by invoking the standard library
function atoi(), passing
in the buffer as the parameter. The return value, the integer value of the text
string, is assigned to the integer variable number and printed on line 13.
On line
15, a new integer variable, sum, is declared, and an attempt is
made to assign to it the result of
adding the integer constant 5 to the
buffer. This, too, fails and is solved by calling the standard function atoi().
NOTE: Some compilers
implement standard conversion procedures (such as atoi()) using macros. You can
usually use these functions without worrying about how they are implemented.
Check your compiler's documentation for details.
qsort()
At times you may want to sort a
table or an array; qsort() provides a quick and easy way to
do so.
qsort()
takes four arguments. The first is a pointer to the
start of the table to be sorted (an array name
works just fine), the second is the number of elements in the table, the third
is the size of each element, and the fourth is a pointer to a comparison
function.
The comparison function must return an int, and must take as its parameters two constant void pointers. void pointers
aren't used very often in C++, as they diminish the type checking, but they
have the
advantage that they can be used to point to items of any type. If you were
writing your own qsort() function,
you might consider using templates instead. Listing 21.7 illustrates how to use
the standard qsort()
function.
Listing 21.7. Using qsort().
1: /* qsort example */ 2:
#include
<iostream.h>
#include
<stdlib.h>
//
form of sort_function required by qsort
int
sortFunction( const void *intOne, const void *intTwo);
9: const
int TableSize = 10; // array size
10:
int
main(void)
{
int
i,table[TableSize];
//
fill the table with values
for
(i = 0; i<TableSize; i++)
{
18: cout << "Enter a number: "; 19: cin >>
table[i];
}
cout
<< "\n";
//
sort the values
qsort((void *)table, TableSize,
sizeof(table[0]), sortFunction);
//
print the results
for
(i = 0; i < TableSize; i++)
28: cout << "Table [" << i <<
"]: " << table[i] << endl; 29:
30: cout
<< "Done." << endl;
}
33:
int
sortFunction( const void *a, const void *b)
{
int
intOne = *((int*)a);
int
intTwo = *((int*)b);
if
(intOne < intTwo)
39: return
-1;
if
(intOne == intTwo)
41: return
0;
return
1;
}
Output: Enter a number:
2
Enter a number: 9
Enter a number: 12
Enter a number: 873
Enter a number: 0
Enter a number: 45
Enter a number: 93
Enter a number: 2
Enter a number: 66
Enter a number: 1
Table[0]: 0
Table[1]: 1
Table[2]: 2
Table[3]: 2
Table[4]: 9
Table[5]: 12
Table[6]: 45
Table[7]: 66
Table[8]: 93
Table[9]: 873
Done.
Analysis: On line 4, the standard library header is included, which is
required by the qsort() function. On line 7, the function sortFunction() is declared, which takes the required four
parameters.
An array
is declared on line 13 and filled by user input on lines 16-20. qsort() is called on line 24, casting the address of the array name table to be a void*.
Note that the parameters for sortFunction are not passed to the call to qsort(). The
name of the sortFunction, which
is itself a pointer to that function, is the parameter to qsort().
Once qsort() is running, it will fill the constant void pointers a and b with each value of the array. If the first value is smaller than the second,
the comparison function must return -1. If it is
equal, the comparison function must return 0. Finally, if the first value is greater than the second value, the
comparison function must return 1. This is reflected in the sortFunction(), as shown
on lines
34 to 43.
Other Libraries
Your C++ compiler supplies a number of other libraries, among them the
standard input and output libraries and the stream libraries that you've been
using throughout this book. It is well worth your time and effort to explore
the documentation that came with your compiler to find out what these libraries
have to offer.
Bit Twiddling
Often you will want to set flags in your objects to keep track of the
state of your object. (Is it in AlarmState? Has this been initialized yet? Are you coming or going?)
You can do this with user-defined Booleans, but when you have many
flags, and when storage size is an issue, it is convenient to be able to use
the individual bits as flags.
Each byte
has eight bits, so in a four-byte long you can
hold 32 separate flags. A bit is said to be "set" if its value is 1, and clear if its value is 0. When
you set a bit, you make its value 1, and
when you clear it, you make its value 0. (Set
and clear are both adjectives and verbs). You can set and clear bits by
changing the value of the long, but
that can be tedious and confusing.
NOTE: Appendix C, "Binary and Hexadecimal," provides
valuable additional information about
binary and hexadecimal manipulation.
C++ provides bitwise operators that act upon the
individual bits.These look like, but are different from, the logical operators,
so many novice programmers confuse them. The bitwise operators are presented in
Table 21.1.
Table 21.1. The Bitwise Operators.
symbol operator
AND
| OR
^
|
exclusive
|
|
OR
|
||
|
||
~
|
complement
|
The AND operator
(&) is a single ampersand, as
opposed to the logical AND, which is two ampersands. When
you AND two bits, the result is 1 if both bits are 1, but 0 if either or both bits are 0. The way
to think of this is: The result is 1 if bit 1
is set and if bit 2 is set.
Operator OR
The second bitwise operator is OR (|). Again, this is a single
vertical bar, as opposed to the logical OR, which is two vertical bars. When you OR two bits, the result is 1 if either bit is set or if both
are.
Operator Exclusive OR
The third bitwise operator is exclusive OR (^). When you exclusive OR two bits, the result is 1 if the
two bits are different.
The Complement Operator
The complement operator (~) clears
every bit in a number that is set and sets every bit that is clear. If the
current value of the number is 1010 0011, the
complement of that number is 0101 1100.
Setting Bits
When you
want to set or clear a particular bit, you use masking operations. If you have
a 4-byte flag and you want to set bit 8 TRUE, you need to OR the flag with the value 128. Why? 128 is 1000 0000 in
binary; thus the value of the eighth bit is 128. Whatever the current value of that bit (set or clear), if you
OR it with the value 128 you will set that bit and not change any of the other bits.
Let's assume that the current value of the 8 bits
is 1010 0110 0010 0110. ORing 128 to it looks
like
this:
9
|
8765
|
4321
|
|
|
|
|
||
1010
|
0110
|
0010
|
0110
|
// bit
|
8
|
is clear
|
||
|
|
|
0000 0000 1000 0000 //
|
128
|
|||||
----------------------
|
|
|
|
|||||
1010
|
0110
|
1010
|
0110
|
// bit
|
8
|
is
set
|
||
There are a few things to note. First, as usual, bits are counted from
right to left. Second, the value 128 is all zeros except for bit 8, the bit you want to set. Third, the
starting number 1010 0110
0010
0110 is left unchanged by the
OR operation, except that bit 8 was set. Had bit 8
already been set, it would have remained set,
which is what you want.
Clearing Bits
If you want to clear bit 8, you
can AND the bit
with the complement of 128. The complement of 128 is
the number you get when you take the bit pattern of 128 (1000 0000), set
every bit that is clear, and
clear every bit that is set (0111 1111). When you AND these numbers, the original number is unchanged, except for the eighth
bit, which is forced to zero.
1010
|
0110
|
1010
|
0110
|
// bit 8
|
is set
|
&
1111 1111 0111 1111
|
// ~128
|
|
|||
----------------------
|
|
||||
1010
|
0110
|
0010
|
0110
|
// bit 8
|
cleared
|
To fully understand this solution, do the math
yourself. Each time both bits are 1, write 1
in the answer. If either bit is 0, write 0
in the answer. Compare the answer with the original number. It should be the
same except that bit 8 was cleared.
Flipping Bits
Finally, if you want to flip bit 8, no matter what its state, you
exclusive OR the number with 128. Thus:
1010
|
0110
|
1010
|
0110
|
// number
|
^
0000 0000 1000 0000
|
// 128
|
|||
----------------------
|
||||
1010
|
0110
|
0010
|
0110
|
// bit flipped
|
^
0000 0000 1000 0000
|
// 128
|
|||
----------------------
|
||||
1010
|
0110
|
1010
|
0110
|
//
flipped back
|
DO set bits by using masks and the OR operator. DO clear bits by
using masks and the AND
operator. DO flip bits
using masks and the exclusive OR operator.
Bit Fields
There are circumstances under which every byte
counts, and saving six or eight bytes in a class can make all the difference.
If your class or structure has a series of Boolean variables, or variables that
can have only a very small number of possible values, you may save some room
using bit fields.
Using the standard C++ data
types, the smallest type you can use in your class is a type char, which
is one byte. You will usually end up using an int, which is two, or more often four, bytes. By using bit fields, you can
store eight binary values in a char and 32
such values in a long.
Here's how bit fields work: bit fields are named and accessed like any
class member. Their type is always declared to be unsigned
int. After the bit field name, write a colon followed
by a number.
The number is an instruction to the compiler as to how many bits to
assign to this variable. If you write 1, the bit will represent either the
value 0 or 1. If you write 2, the bit can represent 0, 1, 2, or 3,
a total of four values. A three-bit field can represent eight values,
and so forth. Appendix C reviews binary numbers. Listing 21.8 illustrates the
use of bit fields.
Listing 21.8. Using bit fields.
#include
<iostream.h>
#include
<string.h>
enum
STATUS { FullTime, PartTime } ;
enum
GRADLEVEL { UnderGrad, Grad } ;
enum
HOUSING { Dorm, OffCampus };
enum
FOODPLAN { OneMeal, AllMeals, WeekEnds, NoMeals };
class
student
{
public:
student():
12:
|
myStatus(FullTime),
|
13:
|
myGradLevel(UnderGrad),
|
14:
|
myHousing(Dorm),
|
15:
|
myFoodPlan(NoMeals)
|
16:
|
{}
|
17:
|
~student(){}
|
18:
|
STATUS
GetStatus();
|
19:
|
void
SetStatus(STATUS);
|
20:
|
unsigned GetPlan() { return myFoodPlan; }
|
21:
|
|
private:
23: unsigned
myStatus : 1;
24: unsigned
myGradLevel: 1;
25: unsigned
myHousing : 1;
26: unsigned myFoodPlan : 2; 27: }; 28:
STATUS
student::GetStatus()
{
31:
|
if
(myStatus)
|
32:
|
return FullTime;
|
33:
|
else
|
34:
|
return PartTime;
|
}
void
student::SetStatus(STATUS theStatus)
{
38: myStatus
= theStatus;
39: } 40:
int
main()
{
44:
|
student
|
Jim;
|
45:
|
|
|
46:
|
if
(Jim.GetStatus()== PartTime)
|
|
47:
|
cout
|
<<
"Jim is part time" << endl;
|
48:
|
else
|
|
49:
|
cout
|
<<
"Jim is full time" << endl;
|
50:
|
|
|
51:
|
Jim.SetStatus(PartTime);
|
|
52:
|
|
|
53:
|
if
(Jim.GetStatus())
|
|
54:
|
cout
|
<<
"Jim is part time" << endl;
|
55:
|
else
|
|
56:
|
cout
|
<<
"Jim is full time" << endl;
|
57:
|
|
|
58:
|
cout
<<
|
"Jim
is on the " ;
|
59:
|
|
|
60:
|
char
Plan[80];
|
|
61:
|
switch
(Jim.GetPlan())
|
|
62:
|
{
|
|
63:
|
case
|
OneMeal:
strcpy(Plan,"One meal"); break;
|
64:
|
case
|
AllMeals: strcpy(Plan,"All meals"); break;
|
65:
|
case
|
WeekEnds:
strcpy(Plan,"Weekend meals");
|
break;
|
|
|
66:
|
case
|
NoMeals:
strcpy(Plan,"No Meals");break;
|
67:
|
default : cout << "Something bad went
wrong!\n";
|
|
break;
|
|
|
68:
|
}
|
|
69:
|
cout
<<
|
Plan
<< " food plan." << endl;
|
70:
|
return
0;
|
|
71: }
|
|
|
Output: Jim is part
time
Jim is full time
Jim is on the No Meals
food plan.
Analysis: On lines 3 to 7, several enumerated types are defined. These
serve to define the possible values for the bit
fields within the student class.
Student is declared in lines 8-27. While this is a trivial class, it is
interesting in that all the data is packed
into five bits. The first bit represents the student's status, full-time or
part-time. The second bit represents whether or not this is an undergraduate.
The third bit represents whether or not the student lives in a dorm. The final
two bits represent the four possible food plans.
The class methods are written as
for any other class, and are in no way affected by the fact that these are bit
fields and not integers or enumerated types.
The member function GetStatus() reads
the Boolean bit and returns an enumerated type, but this is not necessary. It
could just as easily have been written to return the value of the bit field
directly. The compiler would have done the translation.
To
prove that to yourself, replace the GetStatus() implementation with this code:
STATUS
student::GetStatus()
{
return myStatus;
}
There
should be no change whatsoever to the functioning of the program. It is a
matter of clarity when reading the code; the compiler isn't particular.
Note that the code on line 46 must check the status and then print the
meaningful message. It is tempting to write this:
cout << "Jim
is " << Jim.GetStatus() << endl;
That will simply print this:
Jim is 0
The compiler has no way to
translate the enumerated constant PartTime into
meaningful text.
On line
61, the program switches on the food plan, and for each possible value it puts
a reasonable message into the buffer, which is then printed on line 69. Note
again that the switch
statement could
have been
written as follows:
case 0: strcpy(Plan,"One meal"); break; case 1:
strcpy(Plan,"All meals"); break; case 2: strcpy(Plan,"Weekend
meals"); break; case 3: strcpy(Plan,"No Meals");break;
The most important thing about using bit fields is
that the client of the class need not worry about the data storage
implementation. Because the bit fields are private, you can feel free to change
them later and the interface will not need to change.
Style
As stated elsewhere in this book,
it is important to adopt a consistent coding style, though in many ways it
doesn't matter which style you adopt. A consistent style makes it easier to
guess what you meant by a particular part of the code, and you avoid having to
look up whether you spelled the function with an initial cap or not the last
time you invoked it.
The following guidelines are arbitrary; they are
based on the guidelines used in projects I've worked on in the past, and
they've worked well. You can just as easily make up your own, but these will
get you started.
As
Emerson said, "Foolish consistency is the hobgoblin of small minds,"
but having some consistency in your code is a good thing. Make up your own, but
then treat it as if it were dispensed by the programming gods.
Indenting
Tab size should be four spaces.
Make sure your editor converts each tab to four spaces.
Braces
How to align braces can be the most controversial topic between C and
C++ programmers. Here are the tips I suggest:
Matching
braces should be aligned vertically.
The outermost set of braces in a definition or declaration should be at
the left margin. Statements within should be indented. All other sets of braces
should be in line with their leading statements.
No code
should appear on the same line as a brace. For example:
if (condition==true)
{
j = k; SomeFunction();
}
m++;
Long Lines
Keep lines to the width displayable on a single screen. Code that is off
to the right is easily overlooked, and scrolling horizontally is annoying. When
a line is broken, indent the following lines. Try to break the line at a
reasonable place, and try to leave the intervening operator at the end of the
previous line (as opposed to the beginning of the following line) so that it is
clear that the line does not stand alone and that there is more coming.
In C++, functions tend to be far shorter than they were in C, but the
old, sound advice still applies. Try to keep your functions short enough to
print the entire function on one page.
switch Statements
Indent switches as follows to
conserve horizontal space:
switch(variable)
{
case ValueOne: ActionOne(); break;
case ValueTwo:
ActionTwo();
break;
default:
assert("bad Action"); break;
}
Program Text
There are several tips you can use to create code that is easy to read.
Code that is easy to read is easy to maintain.
Use whitespace
to help readability.
Objects and arrays are really referring to one thing. Don't use spaces
within object references (., ->, []).
Unary operators are associated with their operands, so don't put a space
between them. Do put a space on the side away from the operand. Unary operators
include !, ~, ++, --, -, * (for pointers), & (casts),
sizeof.
Binary operators should have spaces on both sides: +, =, *, /, %, >>, <<, <, >, ==, !=, &, |, &&,
||, ?:, =, +=, and so on.
Don't use
lack of spaces to indicate precedence (4+ 3*2).
Put a
space after commas and semicolons, not before.
Parentheses should not have spaces on either side.
Keywords,
such as if, should be set off by a space: if (a == b).
Place the
pointer or reference indicator next to the type name, not the variable name:
char* foo; int& theInt;
● rather
than
char *foo; int &theInt;
● Do not
declare more than one variable on the same line.
Identifier Names
Here are
some guidelines for working with identifiers.
Identifier
names should be long enough to be descriptive.
Avoid
cryptic abbreviations.
Take the
time and energy to spell things out.
Do not
use Hungarian notation. C++ is strongly typed and there is no reason to put the
type
into the variable name. With user-defined types (classes), Hungarian
notation quickly breaks down. The exceptions to this may be to use a prefix for
pointers (p) and references (r), as well as for class member variables (its).
Short names (i, p, x, and so on) should only be used
where their brevity makes the code more readable and where the usage is so
obvious that a descriptive name is not needed.
The length of a variable's name should be proportional to its scope.
Make sure identifiers look and sound different from one another to
minimize confusion.
Function (or method) names are usually verbs or verb-noun phrases: Search(), Reset(), FindParagraph(), ShowCursor().
Variable names are usually abstract nouns, possibly
with an additional noun: count, state, windSpeed, windowHeight. Boolean
variables should be named appropriately: windowIconized, fileIsOpen.
Spelling and Capitalization of Names
Spelling and capitalization
should not be overlooked when creating your own style. Some tips for these
areas include the following:
Use all uppercase and underscore to separate the logical words of names,
such as SOURCE_FILE_TEMPLATE. Note,
however, that these are rare in C++. Consider using
constants and templates in most cases.
All other identifiers should use mixed case--no underscores. Function
names, methods, class, typedef, and
struct names should begin with a
capitalized letter. Elements such as data members
or locals should begin with a lowercase letter.
Enumerated constants should begin with a few lowercase letters as an
abbreviation for the enum. For
example:
enum TextStyle
{
tsPlain,
tsBold,
tsItalic,
tsUnderscore,
};
Comments
Comments
can make it much easier to understand a program. Sometimes you will not work on
a program for several days or even months. In this time you can forget what
certain code does or why it has been included. Problems in understanding code
can also occur when someone else reads your code. Comments that are applied in
a consistent, well thought out style can be well worth the effort. There are
several tips to remember concerning comments:
Wherever
possible, use C++ // comments rather than the /* */ style.
Higher-level comments are infinitely more important than process
details. Add value; do not merely restate the code.
n++; // n is
incremented by one
This comment isn't worth the time it takes to type it in. Concentrate on
the semantics of functions and blocks of code. Say what a function does.
Indicate side effects, types of
parameters, and return values. Describe all assumptions that are made
(or not made), such as "assumes n is
non-negative" or "will
return -1 if x is invalid". Within complex logic, use comments to indicate the conditions
that exist at that point in the code.
Use complete English sentences with appropriate punctuation and
capitalization. The extra
typing is worth it. Don't be overly cryptic and don't abbreviate. What
seems exceedingly clear to you as you write code will be amazingly obtuse in a
few months.
Use blank lines freely to help the reader understand what is going on.
Separate statements into logical groups.
Access
The way you access portions of your program should also be consistent.
Some tips for access include these:
Always
use public:, private:, and protected: labels;
don't rely on the defaults.
List the public members first, then protected, then private. List the
data members in a group after the methods.
Put the constructor(s) first in the appropriate section, followed by the
destructor. List overloaded methods with the same name adjacent to each other.
Group accessor functions together when possible.
Consider alphabetizing the method names within each group and
alphabetizing the member variables. Be sure to alphabetize the filenames in include statements.
Even though the use of the virtual keyword
is optional when overriding, use it anyway; it helps to remind you that it is
virtual, and also keeps the declaration consistent.
Class Definitions
Try to keep the definitions of methods in the same order as the declarations.
It makes things easier to find.
When defining a function, place the return type and all other modifiers
on a previous line so that the class name and function name begin on the left
margin. This makes it much easier to find functions.
include Files
Try as hard as you can to keep from including files into header files.
The ideal minimum is the header file for the class this one derives from. Other
mandatory includes will be
those for objects that are
members of the class being declared. Classes that are merely pointed to
or referenced only need forward references of the form.
Don't
leave out an include file in
a header just because you assume that whatever CPP file includes this one will
also have the needed include.
assert()
Use assert() freely.
It helps find errors, but it also greatly helps a reader by making it clear
what the assumptions are. It also helps to focus the writer's thoughts around
what is valid and what isn't.
const
Use const wherever
appropriate: for parameters, variables, and methods. Often there is a need for
both a const and a
non-const version
of a method; don't use this as an excuse to leave one out. Be very careful when
explicitly casting from const to non-const and vice versa (there are times when
this is
the only way to do something), but be certain that it makes sense, and include
a comment.
Next Steps
You've
spent three long, hard weeks working at C++, and you are now a competent C++
programmer, but you are by no means finished. There is much more to learn and
many more places you can get valuable information as you move from novice C++
programmer to expert.
The following sections recommend a number of specific sources of
information, and these recommendations reflect only my personal experience and
opinions. There are dozens of books on each of these topics, however, so be
sure to get other opinions before purchasing.
Where to Get Help and Advice
The very first thing you will want to do as a C++
programmer will be to tap into one or another C++ conference on an online
service. These groups supply immediate contact with hundreds or thousands of
C++ programmers who can answer your questions, offer advice, and provide a
sounding board for your ideas.
I participate in the C++ Internet newsgroups (comp.lang.c++ and comp.lang.c++.moderated), and I recommend them as excellent sources of information and
support.
Also, you may want to look for local user groups. Many cities have C++
interest groups where you can meet other programmers and exchange ideas.
Required Reading
The very
next book I'd run out and buy and read is
Meyers, Scott. Effective C++
(ISBN: 0-201-56364-9). Addison-Wesley Publishing, 1993.
Magazines
There is one more thing you can do to strengthen your skills: subscribe
to a good magazine on C++ programming. The absolute best magazine of this kind,
I believe, is C++ Report from SIGS Publications. Every issue is packed with
useful articles. Save them; what you don't care about today will become
critically important tomorrow.
You can reach C++ Report at SIGS Publications, P.O.
Box 2031, Langhorne, PA 19047-9700. I have no affiliation with the magazine (I
work for two other publishers!), but their magazine is the best, bar none.
Staying in Touch
If you have comments, suggestions, or ideas about this book or other
books, I'd love to hear them. Please write to me at jliberty@libertyassociates.com, or check out my Web site: www.libertyassociates.com. I look forward to hearing from you.
DO look at other books. There's plenty to learn and no single book can
teach you everything you need to
know. DON'T just read code! The best
way to learn C++ is to write C++ programs. DO
subscribe to a good C++ magazine and join a good C++ user group.
Summary
Today you
saw how some of the standard libraries shipped with your C++ compiler can be
used to manage some routine tasks. Strcpy(), strlen(), and
related functions can be used to manipulate
null-terminated strings. Although these won't work with the string
classes you create, you may find that they provide functionality essential to
implementing your own classes.
The time and date functions allow you to obtain and
manipulate time structures. These can be used to provide access to the system
time for your programs, or they can be used to manipulate time and date objects
you create.
You also learned how to set and test individual bits, and how to
allocate a limited number of bits to class members.
Finally,
C++ style issues were addressed, and resources were provided for further study.
Q&A
They are included for backwards-compatibility
with C. They are not type-safe, and they don't work well with user-created
classes, so their use is limited. Over time, you might expect all of their
functionality to be migrated into C++ specific libraries, at which time the
standard
libraries would become obsolete.
When
would you use bit structures rather than simply using integers?
When the size of the object is
crucial. If you are working with limited memory or with communications
software, you may find that the savings offered by these structures is
essential to the success of your product.
Why do
style wars generate so much emotion?
Programmers become very attached to their habits.
If you are used to this indentation,
if (SomeCondition){
//
statements
}//
closing brace
it is a difficult transition to give it up. New styles look wrong and
create confusion. If you get bored, try logging onto a popular online service
and asking which indentation style works best, which editor is best for C++, or
which product is the best word processor. Then sit back and watch as ten
thousand messages are generated, all contradicting one another.
What is
the very next thing to read?
Tough question. If you want to
review the fundamentals, read one of the other primers. If you want to hone
C++, run out and get Scott Meyers' Effective C++. Finally, if you want to write
for Windows or the Mac, it might make sense to pick up a primer on the
platform.
Is that it?
Yes! You've learned C++,
but...no. Ten years ago it was possible for one person to learn all there was
to know about microcomputers, or at least to feel pretty confident that he was
close. Today it is out of the question: You can't possibly catch up, and even
as you try the industry is changing. Be sure to keep reading, and stay in touch
with the resources that will keep you up with the latest changes: magazines and
online services.
Quiz
1. What is the difference between strcpy() and strncpy()?
What is the function to call to turn an ASCII
string into a long?
What does the complement operator do?
What is the difference between OR and
exclusive OR?
What is the difference between & and &&?
What is the difference between | and ||?
Exercises
Write a program to safely copy
the contents of a 20-byte string to a 10-byte string, truncating whatever won't
fit.
Write a program that tells the current date in the
form 7/28/94.
Write a program that creates 26
flags (labeled a-z). Prompt the user to enter a sentence, and then quickly
report on which letters were used by setting and then reading the flags.
Write a program that adds two
numbers without using the addition operator (+). Hint: use the bit operators!
In Review
The
following program brings together many of the advanced techniques you've
learned during the past three weeks of hard work. Week 3 in Review provides a
template-based linked list with exception handling. Examine it in detail; if
you understand it fully, you are a C++ programmer.
WARNING: If your compiler does not support templates, or if your compiler
does not support try and catch, you will not be
able to compile or run this listing.
Listing R3.1. Week 3 in Review listing.
//
**************************************************
//
//
Title:Week 3 in Review
//
4: //
File: Week3
//
//
Description: Provide a template-based
linked list
7: // demonstration
program with exception
handling
//
//
Classes:PART - holds part numbers and potentially other
10:
|
//
|
information
about
|
parts. This will be
|
the
|
|
|
|
11:
|
//
|
example
class for
|
the
list to hold
|
12:
|
//
|
Note
use of operator<< to print the
|
|
13:
|
//
|
information
about
|
a part based on its
|
14:
|
//
|
runtime
type.
|
|
//
16: // Node
- acts as a node in a List
//
18:
|
//
|
List - template-based list which provides
|
the
|
|
|
19:
|
//
|
mechanisms
for a linked list
|
//
//
//
Author:Jesse Liberty (jl)
//
//
//
Target:Platform independent
//
//
Rev History: 9/94 - First release (jl)
29:
|
//
|
4/97 - Updated (jl)
|
30:
|
//
|
**************************************************
|
31:
|
|
|
32:
|
#include
<iostream.h>
|
|
33:
|
|
|
//
exception classes
class
Exception {};
class
OutOfMemory : public Exception{};
class
NullNode :public Exception{};
class
EmptyList : public Exception {};
class
BoundsError : public Exception {};
//
**************** Part ************
//
Abstract base class of parts
class
Part
{
public:
Part():itsObjectNumber(1)
{}
Part(int
ObjectNumber):itsObjectNumber(ObjectNumber){}
virtual
~Part(){};
int
GetObjectNumber() const { return itsObjectNumber; }
virtual
void Display() const =0; // must be
overridden
private:
int
itsObjectNumber;
};
56:
//
implementation of pure virtual function so that
//
derived classes can chain up
void
Part::Display() const
{
cout
<< "\nPart Number: " << itsObjectNumber << endl;
}
63:
//
this one operator<< will be called for all part objects.
//
It need not be a friend as it does not access private data
//
It calls Display() which uses the required polymorphism
//
We'd like to be able to override this based on the real
type
//
of thePart, but C++ does not support contravariance
{
thePart.Display(); // virtual contravariance!
return
theStream;
}
74:
//
**************** Car Part ************
class
CarPart : public Part
{
public:
CarPart():itsModelYear(94){}
CarPart(int
year, int partNumber);
int
GetModelYear() const { return itsModelYear; }
virtual
void Display() const;
private:
int
itsModelYear;
};
86:
CarPart::CarPart(int
year, int partNumber):
itsModelYear(year),
Part(partNumber)
{}
91:
void
CarPart::Display() const
{
Part::Display();
cout
<< "Model Year: " << itsModelYear << endl;
}
97:
//
**************** AirPlane Part ************
class
AirPlanePart : public Part
{
public:
AirPlanePart():itsEngineNumber(1){};
AirPlanePart(int
EngineNumber, int PartNumber);
virtual
void Display() const;
int
GetEngineNumber()const { return itsEngineNumber; }
private:
int
itsEngineNumber;
};
109:
AirPlanePart::AirPlanePart(int
EngineNumber, int
PartNumber):
itsEngineNumber(EngineNumber),
Part(PartNumber)
{}
void
AirPlanePart::Display() const
{
Part::Display();
cout
<< "Engine No.: " << itsEngineNumber << endl;
}
120:
//
forward declaration of class List
template
<class T>
class
List;
124:
//
**************** Node ************
//
Generic node, can be added to a list
//
************************************
template
<class T>
class
Node
{
public:
friend
class List<T>;
Node
(T*);
~Node();
void
SetNext(Node * node) { itsNext = node; }
Node
* GetNext() const;
T
* GetObject() const;
private:
T*
itsObject;
Node
* itsNext;
};
143:
144: //
Node Implementations...
145:
template
<class T>
Node<T>::Node(T*
pOjbect):
itsObject(pOjbect),
itsNext(0)
{}
151:
template
<class T>
Node<T>::~Node()
{
delete
itsObject;
itsObject
= 0;
delete
itsNext;
itsNext
= 0;
}
//
Returns NULL if no next Node
template
<class T>
Node<T>
* Node<T>::GetNext() const
{
return
itsNext;
}
167:
template
<class T>
T
* Node<T>::GetObject() const
{
if
(itsObject)
return
itsObject;
else
throw
NullNode();
}
176:
//
**************** List ************
//
Generic list template
//
Works with any numbered object
//
***********************************
template
<class T>
class
List
{
public:
List();
~List();
187:
|
|
|
|
188:
|
T*
|
Find(int
|
& position, int ObjectNumber) const;
|
189:
|
T*
|
GetFirst()
|
const;
|
190:
|
void
|
Insert(T
|
*);
|
191:
|
T*
|
operator[](int)
const;
|
|
192:
|
int
|
GetCount()
const { return itsCount; }
|
private:
Node<T>
* pHead;
195:
|
int
|
itsCount;
|
196:
|
};
|
|
197:
|
|
|
//
Implementations for Lists...
template
<class T>
List<T>::List():
pHead(0),
itsCount(0)
{}
204:
template <class
T>
{
delete
pHead;
}
210:
template
<class T>
T* List<T>::GetFirst() const
{
if
(pHead)
return
pHead->itsObject;
else
throw
EmptyList();
}
219:
template
<class T>
T
* List<T>::operator[](int offSet)
const
{
Node<T>*
pNode = pHead;
224:
if
(!pHead)
throw
EmptyList();
if
(offSet > itsCount)
throw
BoundsError();
for
(int i=0;i<offSet; i++)
pNode
= pNode->itsNext;
return pNode->itsObject;
}
236:
//
find a given object in list based on its unique number
(id)
template
<class T>
239: T* List<T>::Find(int
& position, int ObjectNumber) const
{
Node<T>
* pNode = 0;
for
(pNode = pHead, position = 0;
243: pNode!=NULL;
244: pNode
= pNode->itsNext, position++)
{
if
(pNode->itsObject->GetObjectNumber() == ObjectNumber)
247: break;
}
if
(pNode == NULL)
return
NULL;
return
pNode->itsObject;
}
254:
//
insert if the number of the object is unique
template
<class T>
void
List<T>::Insert(T* pObject)
{
Node<T>
* pNode = new Node<T>(pObject);
Node<T>
* pCurrent = pHead;
Node<T>
* pNext = 0;
262:
int
New = pObject->GetObjectNumber();
int
Next = 0;
itsCount++;
266:
if
(!pHead)
{
pHead
= pNode;
return;
}
272:
//
if this one is smaller than head
//
this one is the new head
if
(pHead->itsObject->GetObjectNumber() > New)
{
pNode->itsNext
= pHead;
pHead
= pNode;
return;
}
281:
for
(;;)
{
//
if there is no next, append this new one
if
(!pCurrent->itsNext)
{
287: pCurrent->itsNext = pNode; 288: return;
289: } 290:
//
if this goes after this one and before the next
//
then insert it here, otherwise get the next
pNext
= pCurrent->itsNext;
Next
= pNext->itsObject->GetObjectNumber();
if
(Next > New)
{
298: pNode->itsNext = pNext; 299: return;
}
pCurrent
= pNext;
}
}
304:
305:
int
main()
{
List<Part>
theList;
int
choice;
int
ObjectNumber;
int
value;
Part
* pPart;
while
(1)
{
cout
<< "(0)Quit (1)Car (2)Plane: ";
cin
>> choice;
317:
if
(!choice)
319: break; 320:
cout
<< "New PartNumber?: ";
cin
>> ObjectNumber;
323:
if
(choice == 1)
{
326:
|
cout
<< "Model Year?: ";
|
327:
|
cin
>> value;
|
328:
|
try
|
329:
|
{
|
330:
|
pPart
= new CarPart(value,ObjectNumber);
|
331:
|
}
|
332:
|
catch
(OutOfMemory)
|
333:
|
{
|
334:
|
cout << "Not enough memory; Exiting..."
<< endl;
|
335:
|
return
1;
|
336:
|
}
|
}
else
{
340: cout
<< "Engine Number?: ";
341: cin
>> value;
342: try
{
|
|
344:
|
pPart
= new AirPlanePart(value,ObjectNumber);
|
345:
|
}
|
346:
|
catch
(OutOfMemory)
|
347:
|
{
|
348:
|
cout << "Not enough memory; Exiting..."
<< endl;
|
349:
|
return
1;
|
350:
|
}
|
}
try
{
354: theList.Insert(pPart);
}
catch
(NullNode)
{
358:
|
cout << "The list is broken, and the node is
null!"
|
<< endl;
|
|
359:
|
return
1;
|
}
catch
(EmptyList)
{
363:
|
cout << "The list is empty!" <<
endl;
|
364:
|
return
1;
|
}
}
try
{
for
(int i = 0; i < theList.GetCount(); i++ )
370: cout
<< *(theList[i]);
}
catch
(NullNode)
{
374:
|
cout << "The list is broken, and the node is
null!"
|
<< endl;
|
|
375:
|
return
1;
|
}
catch
(EmptyList)
{
379:
|
cout << "The list is empty!" <<
endl;
|
380:
|
return
1;
|
}
catch
(BoundsError)
{
384:
|
cout << "Tried to read beyond the end of the
list!"
|
<< endl;
|
|
385:
|
return
1;
|
return
0;
}
Output: (0)Quit (1)Car
(2)Plane: 1
New PartNumber?: 2837
Model Year? 90
(0)Quit (1)Car (2)Plane: 2 New PartNumber?: 378 Engine Number?:
4938
(0)Quit
(1)Car (2)Plane: 1
New PartNumber?: 4499
Model Year? 94
(0)Quit (1)Car (2)Plane: 1 New PartNumber?: 3000
Model Year? 93
(0)Quit
(1)Car (2)Plane: 0
Part Number: 378
Engine No. 4938
Part Number: 2837
Model Year: 90
Part Number: 3000
Model Year: 93
Part Number 4499
Model Year: 94
Analysis: The Week 3 in Review listing modifies the program provided
in Week 2 to add templates, ostream processing, and exception handling. The output is identical.
On lines 35-39, a number of exception classes are declared. In the
somewhat primitive exception handling provided by this program, no data or
methods are required of these exceptions; they serve as flags to the catch statements, which print out a very simple warning and then exit. A more
robust program might pass these exceptions by reference and then extract
context or other data from the exception objects in an attempt to recover from
the problem.
On line
44, the abstract base class Part is
declared exactly as it was in Week 2. The only interesting change here is in
the non-class member operator<<(), which
is declared on lines 69-73. Note that this is neither a member of Part nor a friend of part, it simply takes a Part reference as one of its
arguments.
You might want to have operator<< take a CarPart and an AirPlanePart in the
hopes that the correct operator<< would be
called, based on whether a car part or an airplane part is passed.
Since the program passes a pointer to a part, however, and not a pointer
to a car part or an airplane part, C++ would have to call the right function
based on the real type of one of the arguments to the function. This is called
contravariance and is not supported in C++.
There are only two ways to achieve polymorphism in C++: function
polymorphism and virtual functions. Function polymorphism won't work here
because in every case you are matching the same signature: the one taking a
reference to a Part.
Virtual functions won't work here because operator<< is not a member function of Part. You
can't make operator<< a member
function of Part because
you want to invoke
cout << thePart
and that means that the actual call would be to cout.operator<<(Part&), and cout does not
have a version of operator<< that
takes a Part
reference!
To get around this limitation, the Week 3 program uses just one operator<<, taking a reference to a Part. This then calls Display(), which
is a virtual member function, and thus the right version
is
called.
On lines
129-142, Node is
defined as a template. It serves the same function as Node did in the Week 2 Review program, but this version of Node is not tied to a Part object.
It can, in fact, be the node
for any
type of object.
Note that if you try to get the object from Node, and there is no object, this is considered an exception, and the
exception is thrown on line 174.
On lines 181-197, a generic List class template is defined. This List class can hold nodes of any objects that have unique identification
numbers, and it keeps them sorted in ascending order. Each of the list
functions checks for exceptional circumstances and throws the appropriate
exceptions as required.
On lines 306-388, the driver program creates a list of two types of Part objects and then prints out the values of the objects in the list by
using the standard streams mechanism.
0 comments:
Post a Comment