Inapoi
Inainte
Cuprins
3: The C in C++
Since C++ is based on C, you must
be familiar with the syntax of C in order to program in C++, just as you
must be reasonably fluent in algebra in
order to tackle calculus.
If you’ve never seen
C before, this chapter will give
you a decent background in the style of C used in C++. If you are familiar with
the style of C described in the first edition of Kernighan & Ritchie (often
called K&R C), you will find some new and different
features in C++ as well as in Standard C. If you are familiar with Standard C,
you should skim through this chapter looking for features that are particular to
C++. Note that there are some fundamental C++ features introduced here, which
are basic ideas that are akin to the features in C or often modifications to the
way that C does things. The more sophisticated C++ features will not be
introduced until later chapters.
This chapter is a fairly fast coverage of
C constructs and introduction to some basic C++ constructs, with the
understanding that you’ve had some experience programming in another
language. A more gentle introduction to C is found in the
CD ROM packaged in the back of this book, titled
Thinking in C: Foundations for Java & C++ by Chuck Allison (published
by MindView, Inc., and also available at www.MindView.net). This is a seminar on
a CD ROM with the goal of taking you carefully through the fundamentals of the C
language. It focuses on the knowledge necessary for you to be able to move on to
the C++ or Java languages rather than trying to make you an expert in all the
dark corners of C (one of the reasons for using a higher-level language like C++
or Java is precisely so we can avoid many of these dark corners). It also
contains exercises and guided solutions. Keep in mind that because this chapter
goes beyond the Thinking in C CD, the CD is not a replacement for this
chapter, but should be used instead as a preparation for this chapter and for
the
book.
Creating functions
In old (pre-Standard) C, you could call a
function with any number or type of arguments and the compiler wouldn’t
complain. Everything seemed fine until you ran the program. You got mysterious
results (or worse, the program crashed) with no hints as to why. The lack of
help with argument passing and the enigmatic bugs that resulted is probably one
reason why C was dubbed a “high-level assembly
language.” Pre-Standard C programmers just adapted
to it.
Standard C and C++ use a feature called
function prototyping.
With function prototyping, you must use a description of the types of
arguments when declaring and defining a function. This description is the
“prototype.” When the function is called, the compiler uses the
prototype to ensure that the proper arguments are passed in and that the return
value is treated correctly. If the programmer makes a mistake when calling the
function, the compiler catches the mistake.
Essentially, you learned about function
prototyping (without naming it as such) in the previous chapter, since the form
of function declaration in C++ requires proper prototyping. In a function
prototype, the argument list contains the types of arguments that must be passed
to the function and (optionally for the declaration) identifiers for the
arguments. The order and type of the arguments must match in the declaration,
definition, and function call. Here’s an example of a function prototype
in a declaration:
int translate(float x, float y, float z);
You do not use the same form when
declaring variables in function prototypes as you do in ordinary variable
definitions. That is, you cannot say: float x, y, z. You must indicate
the type of each argument. In a function declaration, the following form
is also acceptable:
int translate(float, float, float);
Since the compiler doesn’t do
anything but check for types when the function is called, the identifiers are
only included for clarity when someone is reading the code.
In the function definition, names are
required because the arguments are referenced inside the
function:
int translate(float x, float y, float z) {
x = y = z;
// ...
}
It turns out this rule applies only to C.
In C++, an argument may be unnamed
in the argument list of the
function definition. Since it is unnamed, you cannot use it in the function
body, of course. Unnamed arguments are allowed to give the programmer a way to
“reserve space in the argument list.” Whoever uses the function must
still call the function with the proper arguments. However, the person creating
the function can then use the argument in the future without forcing
modification of code that calls the function. This option of ignoring an
argument in the list is also possible if you leave the name in, but you will get
an annoying warning message about the value being unused every time you compile
the function. The warning is eliminated if you remove the name.
C and C++ have two other ways to declare
an argument list. If you have an
empty
argument list, you can declare it as func( ) in C++, which tells the
compiler there are exactly zero arguments. You should be aware that this only
means an empty argument list in C++. In C it means “an indeterminate
number of arguments (which is a “hole” in C since it disables type
checking in that case). In both C and C++, the declaration func(void);
means an empty argument list. The
void keyword means
“nothing” in this case (it can also mean “no type” in
the case of pointers, as you’ll see later in this
chapter).
The other option for argument lists
occurs when you don’t know how many arguments or what type of arguments
you will have; this is called a variable argument
list.
This “uncertain argument list” is represented by ellipses
(...). Defining a function with a variable
argument list is significantly more complicated than defining a regular
function. You can use a variable argument list for a function that has a fixed
set of arguments if (for some reason) you want to disable the error checks of
function prototyping. Because of this, you should restrict your use of variable
argument lists to C and avoid them in C++ (in which, as you’ll learn,
there are much better alternatives). Handling variable argument lists is
described in the library section of your local C
guide.
Function return values
A C++ function prototype must specify the
return value type of the function (in C, if you leave off the return value type
it defaults to int). The return type specification precedes the function
name. To specify that no value is returned, use the
void keyword. This will
generate an error if you try to return a value from the function. Here are some
complete function prototypes:
int f1(void); // Returns an int, takes no arguments
int f2(); // Like f1() in C++ but not in Standard C!
float f3(float, int, char, double); // Returns a float
void f4(void); // Takes no arguments, returns nothing
To return a value from a function, you
use the
return
statement. return exits the function back to the point right after the
function call. If return has an argument, that argument becomes the
return value of the function. If a function says that it will return a
particular type, then each return statement must return that type. You
can have more than one return statement in a function
definition:
//: C03:Return.cpp
// Use of "return"
#include <iostream>
using namespace std;
char cfunc(int i) {
if(i == 0)
return 'a';
if(i == 1)
return 'g';
if(i == 5)
return 'z';
return 'c';
}
int main() {
cout << "type an integer: ";
int val;
cin >> val;
cout << cfunc(val) << endl;
} ///:~
In cfunc( ), the first
if that evaluates to true exits the function via the return
statement. Notice that a function declaration is not necessary because the
function definition appears before it is used in main( ), so the
compiler knows about it from that function
definition.
Using the C function library
All the functions in your local C
function library are available while you are programming in C++. You should look
hard at the function library before defining your own function –
there’s a good chance that someone has already solved your problem for
you, and probably given it a lot more thought and debugging.
A word of caution, though: many compilers
include a lot of extra functions that make life even easier and are tempting to
use, but are not part of the Standard C library. If you are certain you will
never want to move the application to another platform (and who is certain of
that?), go ahead –use those functions and make your life easier. If you
want your application to be portable, you should restrict yourself to Standard
library functions. If you must perform platform-specific activities, try to
isolate that code in one spot so it can be changed easily when porting to
another platform. In C++, platform-specific activities are often encapsulated in
a class, which is the ideal solution.
The formula for using a library function
is as follows: first, find the function in your programming reference (many
programming references will index the function by category as well as
alphabetically). The description of the function should include a section that
demonstrates the syntax of the code. The top of this section usually has at
least one #include line, showing you the header file containing the
function prototype. Duplicate this #include line in your file so the
function is properly
declared.
Now you can call the function in the same way it appears in the syntax section.
If you make a mistake, the compiler will discover it by comparing your function
call to the function prototype in the header and tell you about your error. The
linker searches the Standard library by default, so that’s all you need to
do: include the header file and call the
function.
Creating your own libraries with the librarian
You can collect your own functions
together into a library. Most programming packages come with a librarian that
manages groups of object modules. Each librarian has its own commands, but the
general idea is this: if you want to create a library, make a header file
containing the function prototypes for all the functions in your library. Put
this header file somewhere in the preprocessor’s search path, either in
the local directory (so it can be found by #include "header") or in the
include directory (so it can be found by #include <header>). Now
take all the object modules and hand them to the librarian along with a name for
the finished library (most librarians require a common extension, such as
.lib or .a). Place the finished library where the other libraries
reside so the linker can find it. When you use your library, you will have to
add something to the command line so the linker knows to
search the library for the functions you call. You must find all the details in
your local manual, since they vary from system to
system.
Controlling execution
This section covers the execution control
statements in C++. You must be familiar with these statements before you can
read and write C or C++ code.
C++ uses all of C’s execution
control statements. These include if-else, while, do-while,
for, and a selection statement called switch. C++ also allows the
infamous goto, which will be avoided in this
book.
True and false
All conditional statements use the truth
or falsehood of a conditional expression to determine the execution path. An
example of a conditional expression is A == B. This uses the conditional
operator == to see if the variable A is equivalent to the variable
B. The expression produces a Boolean true
or false (these are keywords only in C++; in C an expression is
“true” if it evaluates to a nonzero value). Other conditional
operators are >, <, >=, etc. Conditional
statements are covered more fully later in this chapter.
if-else
The if-else statement can exist in
two forms: with or without the else. The two forms are:
if(expression)
statement
if(expression)
statement
else
statement
The “expression” evaluates to
true or false. The “statement” means either a simple
statement terminated by a semicolon or a compound statement, which is a group of
simple statements enclosed in braces. Any time the word “statement”
is used, it always implies that the statement is simple or compound. Note that
this statement can also be another if, so they can be strung
together.
//: C03:Ifthen.cpp
// Demonstration of if and if-else conditionals
#include <iostream>
using namespace std;
int main() {
int i;
cout << "type a number and 'Enter'" << endl;
cin >> i;
if(i > 5)
cout << "It's greater than 5" << endl;
else
if(i < 5)
cout << "It's less than 5 " << endl;
else
cout << "It's equal to 5 " << endl;
cout << "type a number and 'Enter'" << endl;
cin >> i;
if(i < 10)
if(i > 5) // "if" is just another statement
cout << "5 < i < 10" << endl;
else
cout << "i <= 5" << endl;
else // Matches "if(i < 10)"
cout << "i >= 10" << endl;
} ///:~
It is conventional to indent the body of
a control flow statement so the reader may easily determine where it begins and
ends[30].
while
while, do-while, and
for control looping. A statement repeats until the controlling expression
evaluates to false. The form of a while loop is
while(expression)
statement
The expression is evaluated once at the
beginning of the loop and again before each further iteration of the
statement.
This example stays in the body of the
while loop until you type the secret number or press
control-C.
//: C03:Guess.cpp
// Guess a number (demonstrates "while")
#include <iostream>
using namespace std;
int main() {
int secret = 15;
int guess = 0;
// "!=" is the "not-equal" conditional:
while(guess != secret) { // Compound statement
cout << "guess the number: ";
cin >> guess;
}
cout << "You guessed it!" << endl;
} ///:~
The while’s
conditional expression is not restricted to a simple test as in the example
above; it can be as complicated as you like as long as it produces a true
or false result. You will even see code where the loop has no body, just
a bare semicolon:
while(/* Do a lot here */)
;
In these cases, the programmer has
written the conditional expression not only to perform the test but also to do
the
work.
do-while
do
statement
while(expression);
The do-while is different from the
while because the statement always executes at least once, even if the
expression evaluates to false the first time. In a regular while, if the
conditional is false the first time the statement never
executes.
If a do-while is used in
Guess.cpp, the variable guess does not need an initial dummy
value, since it is initialized by the cin statement before it is
tested:
//: C03:Guess2.cpp
// The guess program using do-while
#include <iostream>
using namespace std;
int main() {
int secret = 15;
int guess; // No initialization needed here
do {
cout << "guess the number: ";
cin >> guess; // Initialization happens
} while(guess != secret);
cout << "You got it!" << endl;
} ///:~
For some reason, most programmers tend to
avoid do-while and just work with
while.
for
A for loop performs initialization
before the first iteration. Then it performs conditional testing and, at the end
of each iteration, some form of “stepping.” The form of the
for loop is:
for(initialization; conditional; step)
statement
Any of the expressions
initialization,
conditional, or step
may be empty. The initialization code executes once at the very
beginning. The conditional is tested before each iteration (if it
evaluates to false at the beginning, the statement never executes). At the end
of each loop, the step executes.
for loops are usually used for
“counting” tasks:
//: C03:Charlist.cpp
// Display all the ASCII characters
// Demonstrates "for"
#include <iostream>
using namespace std;
int main() {
for(int i = 0; i < 128; i = i + 1)
if (i != 26) // ANSI Terminal Clear screen
cout << " value: " << i
<< " character: "
<< char(i) // Type conversion
<< endl;
} ///:~
You may notice that the variable i
is defined at the point where it is used, instead of at the beginning of the
block denoted by the open curly brace ‘{’. This is in
contrast to traditional procedural languages (including C), which require that
all variables be defined at the beginning of the block. This will be discussed
later in this
chapter.
The break and continue keywords
Inside the body of any of the looping
constructs while, do-while, or for, you can control
the flow of the loop using break and
continue. break quits the loop without
executing the rest of the statements in the loop. continue stops the
execution of the current iteration and goes back to the beginning of the loop to
begin a new iteration.
As an example of break and
continue, this program is a very simple menu system:
//: C03:Menu.cpp
// Simple menu program demonstrating
// the use of "break" and "continue"
#include <iostream>
using namespace std;
int main() {
char c; // To hold response
while(true) {
cout << "MAIN MENU:" << endl;
cout << "l: left, r: right, q: quit -> ";
cin >> c;
if(c == 'q')
break; // Out of "while(1)"
if(c == 'l') {
cout << "LEFT MENU:" << endl;
cout << "select a or b: ";
cin >> c;
if(c == 'a') {
cout << "you chose 'a'" << endl;
continue; // Back to main menu
}
if(c == 'b') {
cout << "you chose 'b'" << endl;
continue; // Back to main menu
}
else {
cout << "you didn't choose a or b!"
<< endl;
continue; // Back to main menu
}
}
if(c == 'r') {
cout << "RIGHT MENU:" << endl;
cout << "select c or d: ";
cin >> c;
if(c == 'c') {
cout << "you chose 'c'" << endl;
continue; // Back to main menu
}
if(c == 'd') {
cout << "you chose 'd'" << endl;
continue; // Back to main menu
}
else {
cout << "you didn't choose c or d!"
<< endl;
continue; // Back to main menu
}
}
cout << "you must type l or r or q!" << endl;
}
cout << "quitting menu..." << endl;
} ///:~
If the user selects ‘q’ in
the main menu, the break keyword is used to quit, otherwise the program
just continues to execute indefinitely. After each of the sub-menu selections,
the continue keyword is used to pop back up to the beginning of the while
loop.
The while(true) statement is the
equivalent of saying “do this loop forever.” The break
statement allows you to break out of this infinite while loop when the user
types a ‘q.’
switch
A switch statement selects from
among pieces of code based on the value of an integral expression. Its form
is:
switch(selector) {
case integral-value1 : statement; break;
case integral-value2 : statement; break;
case integral-value3 : statement; break;
case integral-value4 : statement; break;
case integral-value5 : statement; break;
(...)
default: statement;
}
Selector is an expression that
produces an integral value. The switch compares the result of
selector to each integral value. If it finds a match, the
corresponding statement (simple or compound) executes. If no match occurs, the
default statement
executes.
You will notice in the definition above
that each case ends with a
break, which causes execution to jump to the end of the switch
body (the closing brace that completes the switch). This is the
conventional way to build a switch statement, but the break is
optional. If it is missing, your case “drops through” to the
one after it. That is, the code for the following case statements execute
until a break is encountered. Although you don’t usually want this
kind of behavior, it can be useful to an experienced
programmer.
The switch statement is a clean
way to implement multi-way
selection (i.e., selecting from among a number of different execution paths),
but it requires a selector that evaluates to an integral value at compile-time.
If you want to use, for example, a string object as a selector, it
won’t work in a switch statement. For a string selector, you
must instead use a series of if statements and compare the string
inside the conditional.
The menu example shown above provides a
particularly nice example of a switch:
//: C03:Menu2.cpp
// A menu using a switch statement
#include <iostream>
using namespace std;
int main() {
bool quit = false; // Flag for quitting
while(quit == false) {
cout << "Select a, b, c or q to quit: ";
char response;
cin >> response;
switch(response) {
case 'a' : cout << "you chose 'a'" << endl;
break;
case 'b' : cout << "you chose 'b'" << endl;
break;
case 'c' : cout << "you chose 'c'" << endl;
break;
case 'q' : cout << "quitting menu" << endl;
quit = true;
break;
default : cout << "Please use a,b,c or q!"
<< endl;
}
}
} ///:~
The quit flag is a
bool, short for
“Boolean,” which is a type you’ll find only in C++. It can
have only the keyword values true or false. Selecting
‘q’ sets the quit flag to true. The next time the
selector is evaluated, quit == false returns false so the body of
the while does not
execute.
Using and misusing goto
The
goto keyword is supported
in C++, since it exists in C. Using goto is often dismissed as poor
programming style, and most of the time it is. Anytime you use goto, look
at your code and see if there’s another way to do it. On rare occasions,
you may discover goto can solve a problem that can’t be solved
otherwise, but still, consider it carefully. Here’s an example that might
make a plausible candidate:
//: C03:gotoKeyword.cpp
// The infamous goto is supported in C++
#include <iostream>
using namespace std;
int main() {
long val = 0;
for(int i = 1; i < 1000; i++) {
for(int j = 1; j < 100; j += 10) {
val = i * j;
if(val > 47000)
goto bottom;
// Break would only go to the outer 'for'
}
}
bottom: // A label
cout << val << endl;
} ///:~
The alternative would be to set a Boolean
that is tested in the outer for loop, and then do a break from the
inner for loop. However, if you have several levels of for or
while this could get awkward.
Recursion
Recursion is an interesting and sometimes
useful programming technique whereby you call the function that you’re in.
Of course, if this is all you do, you’ll keep calling the function
you’re in until you run out of memory, so there must be some way to
“bottom out” the recursive call. In the following example, this
“bottoming out” is accomplished by simply saying that the recursion
will go only until the cat exceeds
‘Z’:[31]
//: C03:CatsInHats.cpp
// Simple demonstration of recursion
#include <iostream>
using namespace std;
void removeHat(char cat) {
for(char c = 'A'; c < cat; c++)
cout << " ";
if(cat <= 'Z') {
cout << "cat " << cat << endl;
removeHat(cat + 1); // Recursive call
} else
cout << "VOOM!!!" << endl;
}
int main() {
removeHat('A');
} ///:~
In removeHat( ), you can see
that as long as cat is less than ‘Z’,
removeHat( ) will be called from within
removeHat( ), thus effecting the recursion. Each time
removeHat( ) is called, its argument is one greater than the current
cat so the argument keeps increasing.
Recursion is often used when evaluating
some sort of arbitrarily complex problem, since you aren’t restricted to a
particular “size” for the solution – the function can just
keep recursing until it’s reached the end of the problem.
 |
|