typedef void *Stack; /* Allocate and deallocate a stack. New_stack() calls malloc. */ extern Stack new_stack(); extern free_stack(Stack); /* Test to see if the stack is empty -- or how many elements it as */ extern int stack_empty(Stack); exter int stack_size(Stack); /* Put something on the stack */ extern stack_push(Stack, Jval); /* Take something off the stack */ extern Jval stack_pop(Stack); /* Look at the top element, but don't take it off */ extern Jval stack_top(Stack);
Here is the C version of the program (c_numrev.c):
#include < stdio.h > #include "c_stack.h" main() { Stack s; int i; s = new_stack(); stack_push(s, new_jval_i(1)); stack_push(s, new_jval_i(2)); stack_push(s, new_jval_i(3)); i = jval_i(stack_pop(s)); printf("First pop: %d\n", i); i = jval_i(stack_pop(s)); printf("Second pop: %d\n", i); free_stack(s); }
The C++ version uses a stack object to implement the program. The class declaration for the stack is as follows (found in stack.h):
class Stack { public: Stack(); // Create a stack ~Stack(); // Destroy a stack void push(Jval); Jval pop(); bool isEmpty(); Jval top(); protected: class StackNode *top_node; };
You may wonder what the Stack and ~Stack methods do. When the new operator is called, it calls an initializer method for the class. Stack is the initializer method. Similarly 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 "stack.h" #include < stdio.h > main() { Stack *s; int i; s = new Stack; s->push(new_jval_i(1)); s->push(new_jval_i(2)); s->push(new_jval_i(3)); i = jval_i(s->pop()); printf("First pop: %d\n", i); i = jval_i(s->pop()); printf("Second pop: %d\n", i); delete s; }
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.
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 (c_printwords.c):
#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 { public: Fields(); // implements new_inputstruct for stdin Fields(const string filename); // implements new_inputstruct for a file ~Fields(); // implements jettison_inputstruct int get_line(); // implements get_line int get_line_number(); // return the current line number int get_NF(); // return the number of fields in the string get_name(); // return the name of the file // current line string get_current_line(); // returns the current line string get_field(int i); // return the ith field in the current line protected: IS file; string name; };
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 (printwords.cpp):
#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_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_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.