stack.h:
typedef struct stk {
int *data;
int top;
} *Stack;
/* Allocate and deallocate a stack. New_stack() calls malloc. */
extern Stack new_stack(int);
extern free_stack(Stack);
/* Test to see if the stack is empty -- or how many elements it as */
extern bool isEmpty(Stack);
/* Put something on the stack */
extern stack_push(Stack, int);
/* Take something off the stack */
extern int stack_pop(Stack);
/* Look at the top element, but don't take it off */
extern int stack_top(Stack);
Here is the C version of the program:
#include < stdio.h >
#include "stack.h"
main()
{
int i;
Stack s;
s = new_stack(5);
stack_push(s, 1);
stack_push(s, 2);
stack_push(s, 3);
i = stack_pop(s);
printf("First pop: %d\n", i);
i = stack_pop(s);
printf("Second pop: %d\n", i);
}
The C++ version uses a stack object to implement the program. The class declaration for the stack is as follows:
class Stack {
Stack(int size);
~Stack();
void push(int);
int pop();
bool isEmpty();
int top();
};
You may wonder what the Stack and ~Stack methods do since I didn't talk about them in class. The meaning of these methods will be further discussed in lab so I'll only touch briefly on them here. You may recall in class I said that when the new operator is called, it calls an initializer method for the class. Stack is the initializer method. Similarly I said the delete operator calls a destructor method. ~Stack is the destructor method. C++ has the idiom that any method that has the same name as the class is an initializer method (called a constructor method in C++) and any method that has the same name as the class but is preceded by a tilde (~) is a destructor method. In this case, the initializer method performs the same tasks as new_stack and the destructor method performs the same tasks as free_stack.
Also notice that the declaration for the Stack class omits any variable declarations. The reason is twofold:
Now that the preliminaries are over, here is the C++ version of the program:
#include < stdio.h >
#include "stack.h"
main()
{
int i;
Stack s;
s = new Stack(5);
s->Push(1);
s->Push(2);
s->Push(3);
i = s->pop();
printf("First pop: %d\n", i);
i = s->pop()
printf("Second pop: %d\n", i);
}
There are three important differences between the C and the C++ programs that you should take note of:
C C++ stack_push(s, 1) s->push(1)
new classname(arguments)new allocates an object that has enough space to store the class's instance variables. It then calls the class's initializer method and passes it the arguments. In this case the Stack initializer takes one argument, which is the size of the stack.
The format of the delete operator is:
delete objectnamedelete first calls the destructor method associated with the object's class, then de-allocates the object's memory.
C++ has a more elegant solution however. Functions can either go into the global namespace or into the namespace of a class. Remember that functions that are not associated with a class are called functions (these functions go into the global namespace), whereas functions that are associated with a class are called methods (these functions go into their class's namespaces). Functions that belong to different namespaces and which have the same name do not conflict with one another. For example, a program could have a push method in the global namespace, a push method associated with a stack class, and a push method associated with a queue class. Hence the program has three different definitions of push, but it is always clear which push function is being used from the context in which push is being called. For example, the call push(x) is a call to the global function because this push is not associated with an object. The call s->push(x) where s is a stack object is a call to a stack's method because the push call is associated with the stack object s.
Although the issue of carving up the namespace may not seem significant, it would if you were working on a real world system with hundreds of thousands or millions of lines of code. In such a system it can be very difficult to come up with distinct function names or to know whether a function name you've come up with has already been used. However, a class typically has at most a few dozen methods (often fewer) so it is much easier to know whether your method name is unique or not.
The second example that illustrates the stylistic differences between C and C++ is a program that reads the lines of a file and prints out each word on a separate line, preceded by its line number. For example, given the input:
Chocolate is nice but ice cream is dandythe program should produce the output:
1: Chocolate 1: is 2: nice 2: but 2: ice 3: cream 3: is 3: dandy
We are going to use Dr. Plank's fields library to read lines from the file. To refresh your memory, here is the fields.h file:
define MAXLEN 1001
#define MAXFIELDS 1000
typedef struct inputstruct {
char *name; /* File name */
FILE *f; /* File descriptor */
int line; /* Line number */
char text1[MAXLEN]; /* The line */
char text2[MAXLEN]; /* Working -- contains fields */
int NF; /* Number of fields */
char *fields[MAXFIELDS]; /* Pointers to fields */
int file; /* 1 for file, 0 for popen */
} *IS;
extern IS new_inputstruct(IS);
extern int get_line(IS); /* returns NF, or -1 on EOF. Does not
close the file */
extern void jettison_inputstruct(IS); /* frees the IS and fcloses
the file */
Using the fields library, here is the C Version of printwords:
#include < stdio.h >
#include "fields.h"
main(int argc, char **argv)
{
IS is;
int i;
if (argc != 2) {
fprintf(stderr, "usage: printwords filename\n");
exit(1);
}
is = new_inputstruct(argv[1]);
// right here we should check whether is is null and
// report an error if it is but the C++ code handles
// this case slightly differently (and in a way that
// is irrelevant here) so the check is omitted
while(get_line(is) >= 0) {
for (i = 0; i < is->NF; i++) {
printf("%d: %s\n", is->line, is->fields[i]);
}
}
jettison_inputstruct(is);
exit(0);
}
The C++ version uses an object-oriented version of Dr. Plank's fields library. In particular, we declare a Fields object that contains both the data of an inputstruct and the functions provided by the fields library. However, in true object-oriented fashion, the functions and the data are packaged together in an object and the functions are called methods. Also, at this point I do not want to get too wound up in precise C++ syntax so the following C++ declaration for the Fields class is only approximate:
class Fields {
Fields(const string filename); // implements new_inputstruct
~Fields(); // implements jettison_inputstruct
int get_next_line(); // implements get_line
int get_line_number(); // return the current line number
int get_NF(); // return the number of fields in the
// current line
string get_field(int i); // return the ith field in the current line
}
Note that the Fields and ~Fields methods are initializer and destructor methods respectively and that they do the work of new_inputstruct and jettison_inputstruct.
Also, like the Stack class declaration, the declaration for the Fields class omits any variable declarations. The reason is twofold:
Now that the preliminaries are finished, here is the C++ version of printwords:
#include < stdio.h >
#include "fields.h"
main(int argc, char **argv)
{
Fields *is;
int i;
if (argc != 2) {
fprintf(stderr, "usage: printwords filename\n");
exit(1);
}
// get a new Fields object
is = new Fields(argv[1]);
// print out the words prefixed by a line number
while(is->get_next_line() >= 0) {
for (i = 0; i < is->get_NF(); i++) {
printf("%d: %s\n", is->get_line_number(), is->get_field(i).c_str());
}
}
// destroy the Fields object
delete is;
exit(0);
}
There are four important differences between the C and the C++ programs that you should take note of:
C C++ get_line(is) is->get_next_line()
is->get_field(i)->c_str()This call is parsed as follows: (is->get_field(i))->c_str(). In other words, is's get_field method is called first. If you look at the class declaration for Fields, you will see that get_field returns a string. So is->get_field(i) returns a string object. This string object's c_str method is then called. c_str returns a char *. The reason the string's c_str method must be called is that printf knows how to print char *'s but it does not know how to print strings. The sequence of two method calls should help reinforce your notion of C++ as being object-oriented. The object is gets passed the called to get_field. This call results in a string object being returned. In turn, this string object gets passed the call to c_str, which results in a char * being returned.