Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Collections in Turbo C++


AUG90: COLLECTIONS IN TURBO C++

Bruce Eckel

Bruce is the author of Using C++ (Osborne/McGraw-Hill, 1989), a voting member of the ANSI C++ committee (X3J16), and the owner of Revolution 2, a firm specializing in C++ training and consulting. He is the C++ editor/columnist for The C Gazette and was a contributing writer for MicroCornucopia magazine for four years. Portions of this article are based on the as-yet-unpublished The Tao of Objects, by Bruce Eckel and Gary Entsminger.


According to the unwritten rules of implementing C++, compiler vendors have the option of saying "sorry, not implemented" when a language feature is either too difficult to implement or when the implementor feels that the programmer really doesn't need a particular feature. Most implementors have invoked this rule at one time or another, even AT&T with its cfront translator. Furthermore, vendors sometimes implement features for which a compiler may accept the syntax but not enforce it; const and volatile member functions are good examples of this. In such cases, you may think you're getting the benefit of a feature when in fact you aren't.

Borland's Turbo C++ is the finest implementation of a C++ compiler I have seen. This article examines how successful Borland was in implementing C++, using as example C++ 2.0 features such as multiple inheritance and pointers to members to create a "collection" class that counts keywords and identifiers in a C++ program.

Surveying the Turbo C++ Landscape

Turbo C++ provides full ANSI C compatibility of Turbo C 2.0 and is fully compliant with the C++ 2.0 Draft Reference Manual (see the accompanying text box for a description of C++ 2.0, 2.1, and the ANSI C++ committee). Of particular interest is Turbo C++'s support for multiple inheritance, type-safe linkage, pointers to members, and built-in support for abstract classes. Borland has also implemented the AT&T C++ 2.0 iostream package, as well as the older C++ 1.0 streams (for backward compatibility with old code) and the complex class.

Turbo C++ comes with a library of classes intended to help you build programs, including useful classes such as Dictionary and HashTable. Class libraries address the common complaint among C++ programmers (to wit: "Fine, C++ makes code reuse easier. Where's the code to reuse?"). However, Borland made the assumption that what was good design for Smalltalk would be good design for C++. Thus we have the ponderous Smalltalk-like hierarchy, where everything is derived from a base class Object (no use is made of multiple inheritance), the sometimes irritating Smalltalk names such as theQueue and hash ValueType (they've also made the assumption that you should be able to ask any object what type it is, rather than letting the object worry about it's own behavior), and some rather strange-looking syntax (definitely not for novices). I would have liked it better had Borland followed AT&T's lead and included a task library instead.

On the positive side, there's source code for everything, the manual is on disk, and, of course, you aren't forced to use the libraries. Simple examples show how to use some of the classes (a sorted directory listing program, for instance).

Other Features

You can easily generate assembly output with Turbo C++. This can be very helpful, especially if you want to hand-optimize code or create your own assembly language routine to link into a C++ program. The name-mangling scheme used is very easy to read (an aside: When you get linker error messages, the names are automatically unmangled for you. Nice!).

The Turbo C library is available in Turbo C++. You can link with assembly language and other libraries, including all those created for the Borland environment (that is, Turbo Pascal and Turbo Assembler) or with Microsoft C. Paradox Engine code can be linked in with Turbo C++, providing a framework for some powerful C++ database applications. And I understand you can even link to Turbo Prolog object modules (even though Turbo Prolog has reverted to the Prolog Development Center, its original creator). As usual, Borland encourages third-party support.

The asm() directive in C++ is supported in Turbo C++; this allows inline assembly code. You have full control of the expansion of inline C++ functions; you can turn inlining off if you want (very convenient if you go crazy with inlines and later come to your senses). The compiler merges literal strings to generate smaller programs. You have full control of the way virtual tables are generated. Borland has been careful not to do anything to prohibit embedding Turbo C++ code.

More Details.

Turbo C++ uses the VROOMM overlay manager for building big programs using EMS/disk overlays. Sadly, it doesn't support the equivalent of Zortech's __handle pointers (see "Getting a Handle on Virtual Memory," by Walter Bright, DDJ, May 1990) to do the same thing for data. I think the __handle pointer is one of Walter Bright's more terrific ideas. I'm hoping Borland will add this in the future.

The ability to set the library, include information, and command-line flags in the TURBOC.CFG file is excellent! This immediately eliminates all the path collision and environment-space problems you have when running more than one C++ compiler on your machine (I do this when I want to test correctness). Turbo C++ will also swap the symbol table to extended memory or EMS for compilations.

Although (as with all C++ implementations) there are limitations on what the C++ compiler will actually inline, there are far fewer restrictions on what the compiler will accept in an inline statement. For instance, Turbo C++ would accept static variables and "embedded" return statements (those that aren't at the end of the function) in inline functions, while Zortech and cfront wouldn't.

Programmer's Platform

A Borland language product wouldn't be complete without the Integrated Development Environment (IDE). The IDE includes everything you need to edit, compile, link, and debug programs. This time, however, the IDE has been completely reworked with multiple overlapping windows you can reshape and move around the screen. Other features include a hypertext-style help system, the ability to copy code fragments from the help system to the editor, and more. For those of you who can't seem to get by without rodents, there's full mouse support. And, a nice interactive tour teaches you how to use the IDE features.

The "Turbo Editor Macro Language" (TEML), a very readable scripting language that looks like a bastardization of Pascal and C, lets you use macro scripts to write customizations. For instance, you can rebind all your keys when the editor starts up, grab class names and member function prototypes, pop to another file and append the class name to a :: and the function prototype to create a definition stub, and so on. The macros are compiled with the "Turbo Editor Macro Compiler" (TEMC), which avoids many of the speed problems associated with interpreted macro languages. You can create named macros (with arguments) and you can bind those macros to keys; you can also just tie a set of statements to a key without a name. There are over 140 built-in commands to work with. Example 1 shows a simple example of a macro which puts void at the beginning of a line, then returns to the point where you invoked it. Unfortunately, it is difficult to determine from the documentation the limitations of TEML.

Example 1: Macro that puts void at the beginning of a line, then returns to the point where you invoked it.

  macro InsertVoid
        SetMark (0);  /* save our place */
        LeftOfLine;
        InsertText ("void");
        MoveToMark (0); /* restore the place */
  end; /* end of macro InsertVoid */

  Alt-V: InsertVoid;    /* bind to key */

The IDE also has a built-in, small version of the Turbo Debugger, which displays C++ source code as you've typed it in (no messes from name mangling) and allows you to perform various debugging feats.

Turbo Professional

The full-blown debugger (provided with the Turbo Professional, or sold separately with the Turbo Debugger and Tools package) is a step forward in debugger technology. It's better than anything I've seen (although I admit I never had the patience to figure out CodeView). I didn't need to use the manual much at all, since the menus and help system are so good. The debugger can use 80386 protected mode by loading a special device driver; this allows viewing assembly language after a hard crash. I particularly like the traceback feature; you can use the animation feature to automatically step until the program crashes, then trace back and see its last thoughts (although it would be nice if you could speed up animation in this process by eliminating screen updates). The display of class hierarchy is fine, but it isn't much use by itself; perhaps the debugger is the wrong place for it -- you want to see hierarchies while you're writing code, not while you're debugging it.

The Turbo Profiler (also part of the professional package) should alleviate any concerns you have about potential performance differences with C. It seems to me the benefit of optimizers is overstated. An optimizer doesn't have enough information to make a large difference in execution speed. If you can find the place where your program is spending all its time, you can go in and optimize that section by hand, rather than letting the compiler sprinkle potentially useless speed improvements throughout the program. You can think of the profiler as attacking the problem from the opposite end, and the improvements in speed are potentially far more dramatic than what you will see with an optimizer. Don't underestimate the value of this tool.

Taking Up A Collection

The code presented in this section demonstrates several concepts programmers new to C++ should understand. These concepts include:

  • Collections, which support a dynamic style of programming;
  • Multiple inheritance, so that classes can be derived from more than one base;
  • Pointers to members, which let you delay the selection of a member (function or data) until run time.
The classes created here are simple but powerful. The example that illustrates them is used to count the keywords and identifiers in a C++ source file.

A collection is an object which holds an arbitrary number of other objects. Basically, it's just a bag you can throw things into and fish them out later. The reason collections are so important is that they support a dynamic style of programming. You can't always determine how many and what type of objects you need while you're writing a program (although you may be in the habit of trying to figure it out). In the general case, these things can only be established at run time. Thus, objects must be created and destroyed on-the-fly, using dynamic object creation (via the operators new and delete). While you're working with these objects, you need some place to stash them -- that's where the collection comes in.

You're probably familiar with linked lists, stacks, queues, and trees; these are all prototypical collections in non-object-oriented languages. The collection in Listing One, page 132, looks vaguely like a singly-linked list. I like it precisely because it is so simple, but you may have to stare at it a while to figure out exactly how it works.

The first definition is a struct called item, which only contains a virtual destructor. Any object you want to put into a collection should be derived from item so that collection can "own" the items it holds: When it wants to destroy the item, the proper destructor is called (note that ownership isn't always so clear cut -- in some situations an object is contained in more than one list).

A collection is a chain of holder objects. Each holder is a link in the chain; it has a pointer to an item and a pointer to the next holder. Initializing a holder is simply setting its two pointers. Note that although struct holder is defined inside class collection, holder is a global name in C++ 2.0 (but hidden in C++ 2.1).

A collection object just contains two holder pointers -- one called head, which points to the top of the list, and one called cursor, which moves through the list and points to the holder we're currently interested in. Initializing a collection means setting its two pointers to NULL (to indicate the list is empty). Cleaning up the list is more complex; we must go to the head and move through the list until it's empty, deleting each item and holder in the list. Here you'll need to ponder the code and the comments, imagining what's going on. Sometimes it helps to draw pictures.

Adding a new item to a collection is the slickest part of the whole class. Make a new holder using the current head pointer and the new item pointer as arguments. The value returned by new (the address of the newly created holder) becomes the new value for head. In one statement the list expands, just like yeast budding a new cell (this is the add() function). Because add() is inline, adding an element is amazingly fast.

Stepping Through a Collection

You step through a collection using the reset() and next() commands, each of which returns a pointer to the current item, or NULL if we're at the end of the list. For a collection c, use the commands as shown in Example 2. If the list is empty, reset() returns NULL and the while loop is never entered. Note that in both reset() and next(), we must take care not to return an item* if cursor is NULL; this is taken care of by the ternary if-else statement (? and :). In next(), we must not move forward if cursor is NULL.

Example 2: Stepping through a typical collection.

  item * i = c.reset();
  while(i)  {
    // do something
    i = c.next ();
  }

Multiple Inheritance

In a "pure" object-oriented language such as Smalltalk, everything is an object. All objects have as their base the same class (called "object" or "root" or something like that). Collections in Smalltalk are usually part of the existing system; you don't have to make them yourself. They are designed to hold any type of object, thus they usually hold root objects, or something close to root in the inheritance tree.

The need for multiple inheritance is not obvious in a system like this; multiple inheritance allows you to combine the characteristics of two or more classes, but in Smalltalk all objects already have a lot of things in common. Now, consider C++ where classes are often made from scratch instead of being inherited from some master root class. What happens if you have two predefined classes (which you can't or don't want to change) that must be combined into one? As an example, consider a relatively simple class that holds a word and compares it to text strings, incrementing a counter if there is a match. Since we're defining the word class right here we could have inherited it from item, but imagine it has been created by someone else and is much more complicated.

Now suppose you want to make a collection of word objects. Anything that goes in a collection must be derived from item so the collection can "own" it and properly destroy it. But word has already been created, so it can't be derived from item. What we need is a way to derive a new type from word and item at the same time. That's where multiple inheritance is essential in C++. You can see it used in the creation of class worditem, which is a word that can also be stored in a collection. The first main() in Listing One is a simple demonstration of a collection of worditems.

Counting Words

If we want to count instances of words instead of just adding each word to the list, we need to modify the collection class by inheriting it into a new class called wordcounter. This class is first customized so that add() only accepts worditem pointers, and reset() and next() only return worditem pointers. Although this may look as if you're adding code, there's actually no over-head -- the compiler does all the work by enforcing type checking. We then add a new function, add_or_count(), to class wordcounter. This will test a string against each word in the list; if it finds a match, that word gets incremented; if not, it adds the word to the list.

Pointers to Members

Finally, we add a function called apply, which uses pointers to members. These are particularly useful for collections -- in apply(), the function argument is applied to each member of the list. Note that a pointer to a member function looks a lot like a pointer to a function; it just has the class name and the scope resolution operator added. The apply() function is demonstrated in the second main().

You can imagine a more powerful construct: Consider an array of pointers to member functions. You could index into this array to select the desired member. Pointers to members can also be used to change the behavior of a "call back." For example, with a mouse event that might cause the system to change state, the next mouse event should cause something different to happen.

Products Mentioned:

Turbo C++ Borland International P.O. Box 660001 Scotts Valley, CA 95066-0001 $199.95 $299.95 for Turbo C++ Professional Special prices for registered owners of Turbo C.

Counting Identifiers and Keywords

Now that we have the desired class and the desired collection, let's use them to count the identifiers and keywords in a C++ source code file (I tested it on Listing One). The trick is simply to remove all the quoted strings, operators, comments, and constants and put the remaining words into our wordcounter collection by using add_or_count(). This is accomplished by using the ANSI C library functions strchr(), which finds the first instance of a character in a string, strstr(), which finds the first instance of a string within a string, strtok(), which breaks a string up into tokens according to the delimiters of your choice, and strcspn(), which counts the characters in the initial length of a string that doesn't consist of characters from your chosen string; this is used to determine if a string is a numerical constant. Look these up in your ANSI C library reference for further details.

I'm using standard I/O here instead of bothering to open and close files. You have to redirect input and output on the command line, but it makes the example simpler.

In running TEST3, notice that the results of the analysis are printed to standard output as the destructors are called for the worditem objects (the print statement is in the word destructor).

You can easily create a second type of list to hold keywords. Initialize this list with the C++ keywords, then test each new word against this list before conditionally adding it to the symbol_table list. This way you can separate keywords and identifiers.

Note that parts of Listing One will compile with Zortech C++ 2.06 (if you remove the pointers to members, the whole thing will compile). However, the TEST1 code shows a place where Zortech C++ 2.06 has a bug (it works properly with cfront 2.0 and Turbo C++).

I believe Borland will capture the C++ market in much the same way they did the Pascal market. They are just out of the blocks with Turbo C++ and already far ahead of everyone else. Other C++ vendors will have to come up with something pretty spectacular for me to pry my clenched fingers from Turbo C++.

C++ Release 2.0

In May 1989, Bjarne Stroustrup (the creator of C++) and the team at AT&T announced C++ release 2.0 and provided the C++ 2.0 Draft Reference Manual (commonly referred to as the DRM). This was a significant upgrade to the earlier release 1.2. The largest changes were the addition of multiple inheritance and type-safe linkage (which, although invisible to the programmer, prevents one of C's more subtle and obnoxious errors), but a number of other smaller gems added to the language make a significant difference in what we can do.

Class-by-class Overloading of Operator new and delete In release 1.2, you could only change the activity of the dynamic object creation operator new() (and its complement, operator delete()) on a global basis. For example, overloading new and delete allows you to provide a more efficient memory-allocation scheme. This is generally something you only want to do for an individual class (one you allocate and deallocate a lot of objects for) so this is a significant improvement (also, you can forget about "assignment to this"). You can also create a garbage collector for an individual class; calling new for that class can register the object with your garbage collector. You can now specify the physical location in memory where you want an object to be placed (especially useful in embedded systems or other hardware-specific situations).

Support for Abstract Classes An abstract class is a base class from which you inherit a number of derived classes. It establishes the "common interface" to a set of polymorphic classes. For instance, you might create a class sortable, which defines a type of object that can be sorted. You never make any sortable objects; instead you make objects of classes derived from sortable (i.e., record). To prevent the user of a class from creating a sortable object, C++ 2.0 allows you to create "pure" virtual functions by setting the function body to zero, as in virtual int sort() = 0;. The compiler won't allow you to create any instances of a class containing a pure virtual function.

Copying and Initialization If you forget, or you don't want to define an operator() or a copy-constructor X(X&) for your class, the 2.0 compiler now does it for you. These functions have always been a source of confusion for new C++ programmers, but they are important when copying an object or passing it by value into or out of a function.

More Operator Overloading You can now overload the comma operator and the -> operator (sometimes called a "smart pointer").

Const, Volatile, and Static Member Functions These modifiers allow you to change the way the compiler treats class member functions. A const member function cannot change the member data of an object, nor call another function that does. Thus it can be called for a const object. Similarly, a volatile member function can be called for a volatile object; the compiler must assume the object may be changed by outside forces and thus cannot make any assumptions about object stability during optimization of a volatile member function. A static member function is like an ordinary function except it is part of the class, so its name is hidden and it can only be called for that class. However, it cannot access any nonstatic members of an object (either functions or data).

Sophisticated Initialization Among the forms of initialization in 2.0, you can initialize automatic and global ("static") objects using all kinds of complicated expressions. For example, if record and book are both derived from sortable, you can write:

  sortable * s[] = {
     new record;
     new book;
  };

Pointers to Members

You can take the offset of a class member and pass it into a function, which can then use that offset to call a member function or modify member data. This is often used in a function which acts like "apply" in Lisp -- it will take the member function of your choice and apply it to each object in a list. This feature has actually been part of the language before release 2.0, but has not been universally supported (Zortech 2.06, for instance, doesn't support pointers to members; Zortech 2.1 does).

C++ 2.1 and ANSI C++

Release 2.0 was to have been the specification for the language until the time that the ANSI C++ committee (X3J16) came out with ANSI C++. However, Stroustrup found some small problems with the language and changed them while in the process of writing the Annotated C++ Reference Manual (by Bjarne Stroustrup and Margaret Ellis, Addison-Wesley, 1990). The ARM, as the Annotated Reference Manual is called, serves as the primary base document for the ANSI C++ committee. AT&T has released version 2.1 of its cfront C-code generator (which takes C++ and generates C; these have sometimes been called translators, as opposed to native-code compilers like Turbo C++ and Zortech C++). All this happened rather suddenly, so the version of Turbo C++ I had conformed to the DRM (2.0) rather than the ARM (2.1). However, the changes are small so I expect conformance to 2.1 fairly soon; not having the changes in release 2.1 isn't inconvenient and you may not even notice them unless you already know the language quite well.

Additional Changes In 2.1

Classes, enumerations, and typedefs defined within a class are now local to the class. The conversion from a pointer to derived-class object to pointer to base-class object has been clarified. A class with a copy-constructor may be passed by value to a function with an ellipses argument (although a bitcopy is done, rather than a call to the copy-constructor). You now delete arrays with delete []p;. Notice that the number of objects in the array is no longer necessary. An object which is created under a condition must be destroyed under that condition, and cannot be accessed outside that condition (for example, in if(x) for(int i = 10; i; i--) foo(i); you cannot access i for the rest of the scope).

Pure virtual functions are implicitly inherited as pure. Calling a pure virtual function inside a constructor produces undefined behavior, rather than an error message. Constructors with all default values are now considered "default" constructors (they can be used in places where a constructor with no arguments is required, for example, arrays definitions). You can define built-in types as if they had constructors; for example, int i(5); (similarly for destructor calls).

Resolution of function overloading has been clarified and improved. You can distinguish between pre- and post-fix operator ++ and --. Unions can have private and protected members. delete cannot be used on a pointer to a const. friend functions are forced to be extern (publicly visible). Base classes may be inherited as protected. An explicit destructor call p->X:: ~ X() calls X's destructor even if it's virtual, while p-> ~ X() uses the virtual mechanism.

Your Comments and Suggestions

The ANSI C++ committee had its first meeting March 12 - 16 in New Jersey and the second July 9 - 12 in Seattle. Written public suggestion and comment is invited (in fact, it's an integral part of the process). The committee expects to finish its work in roughly three years (since it has a running start with the ARM and the ANSI/ISO C specs). The major extensions that will likely be added by the ANSI committee include exception handling and parameterized types (implementations of which you may see before the committee is complete). Send suggestions and comments to:

  Dmitry Lenkov
  X3J16 Committee Chair
  HP California Language Lab
  19447 Pruneridge Avenue, MS: 47LE
  Cupertino, CA 95014
  email:dmitry%[email protected]

-- B.E.

_COLLECTIONS IN TURBO C++_ by Bruce Eckel

[LISTING ONE]

<a name="01bc_0017">

// COLLECT.CPP : collection example, with multiple inheritance and
// pointers to members.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct item {
  virtual ~item() {} // so collection can properly destroy what it holds
};

// Suppose we have a pre-existing class to hold items:
class collection {
  // in 2.0, "holder" is a global name.  In 2.1, it's hidden:
  struct holder {
    holder * next;  // link to next holder
    item * data; // pointer to actual data
    holder(holder * nxt, item * dat)  // constructor
      : next(nxt), data(dat) {}
  } *head, *cursor;
public:
  // initialize an empty list:
  collection() : head((holder *)NULL), cursor((holder *)NULL) {}
  // clean up the list by removing all the elements:
  ~collection() {
    cursor = head;
    while(cursor) {  // while the list is not empty ...
      delete cursor->data;  // delete the current data
      head = cursor->next;  // head keeps track of where the next holder is
      delete cursor;  // delete the current holder
      cursor = head; // move to the next holder
    }
  }
  // Paste a new item in at the top (this is tricky):
  void add(item * i) { head = new holder(head, i); }
  // reset() and next() return the current item or null, for the list's end:
  item * reset() { // go back to the top of the list
    cursor = head;
    // the list may be empty; only return data if cursor isn't null
    return cursor ? cursor->data : (item *)NULL;
  }
  item * next() {
    // Only move forward if cursor isn't null:
    if(cursor) cursor = cursor->next;
    // only return data if cursor isn't null:
    return cursor ? cursor->data : (item *)NULL;
  }
};

// Now suppose we have a second pre-exising class to hold words,
// keep track of word counts, and print itself in 2 ways:
class word {
  char * w;
  int count;
public:
  word(char * wd) : count(1) {
    w = new char[strlen(wd) + 1];  // allocate space for word
    strcpy(w,wd);  // copy it in
  }
  ~word() {
    printf("%s : %d occurrences\n", w, count);
    delete w;  // free space for word
  }
  int compare(char * testword) {
    int match = !strcmp(w, testword);
    if(match) count++; // count testword if it matches
    return match;
  }
  void print1() { printf("%s\n", w); } // 1 per line
  void print2();
};

// Zortech 2.06 and cfront 2.0 wouldn't allow the following
// function as an inline; Turbo C++ would.
void word::print2() {  // print several words per line

  static p1cnt;  // count words on a line
  const words_per_line = 7;
  printf("%s ", w);
  if(++p1cnt % words_per_line) return;
  putchar('\n'); // only when remainder is 0
}

// What if we want to make a collection of words?  Multiple
// Inheritance to the rescue:
class worditem : public item, public word {
public:
  worditem(char * wrd) : word(wrd) {}
};

// now we can create a collection of worditems.  Here's an array
// of words to put in our collection:
char * words[] = { "this", "is", "a", "test", "of", "worditem" };

#ifdef TEST1
main() {
  collection c;
  for(int i = 0; i < sizeof(words)/sizeof(words[0]); i++)
    c.add(new worditem(words[i]));
  // NOTE: Zortech C++ 2.06 doesn't work here.
}
#endif // TEST1

// But now we want to count instances of words.  We need to modify the
// collection class so it conditionally adds a word, or just counts it
// if it already exists:

class wordcounter : public collection {
public:
  // Customize for worditems (no overhead):
  void add(worditem * wi) { collection::add(wi); }
  worditem * reset() { return (worditem *)collection::reset(); }
  worditem * next() { return (worditem *)collection::next(); }
  void add_or_count(char * newword) {
    worditem * cur = reset();
    while(cur) {
      // if found, increment the count and quit the search:
      if(cur->compare(newword)) return;
      cur = next();
    }
    // at this point, we didn't find it, so add it to the list:
    add(new worditem(newword));
  }
  // Pointers to members (Zortech 2.06 doesn't support this):
  void apply(void (word::*pmf)()) {
    worditem * wit = reset();
    while(wit) {  // do while list is not empty
      (wit->*pmf)();  // dereference member function pointer
      wit = next();  // get next list element
    }
  }
};

char * words2[] = { "this", "this", "is", "a", "test", "test", "test" };

#ifdef TEST2
main() {
  wordcounter wc;
  for(int i = 0; i < sizeof(words2)/sizeof(words2[0]); i++)
    wc.add_or_count(words2[i]);
  // Now "apply" two different functions to the list:
  wc.apply(&word::print1);
  wc.apply(&word::print2); putchar('\n');
}
#endif // TEST2

// Now, for fun, let's use this class to count the keywords and
// identifiers in a C++ program.  Try this program on itself:
//    collect < collect.cpp > count
// Look up strstr(), strchr() and strtok() in your ANSI C
// library guide.

const char * delimiters = " \t#/(){}[]<>.,;:*+-~!%^&=\\|?\'\"";
const char * digits = "0123456789.";

#ifdef TEST3
main() { // use i/o redirection on the command line.
  wordcounter symbol_table;
  char buf[120];
  while (gets(buf)) { // get from standard input
    if(*buf == '#') continue;  // ignore preprocessor lines
    // strip all quoted strings in the line:
    char * quote = strchr(buf, '\"');  // find first quoted string
    while(quote) {
      if(quote[-1] == '\\') break; // for \" literal quote
      *quote++ = ' ';  // erase quote
      while(*quote != '\"' && *quote != 0)
        *quote++ = ' '; // erase contents of string
      *quote = ' '; // erase ending quote
      quote = strchr(quote, '\"'); // look for next quoted string
    }
    char * cmt = strstr(buf, "//");  // C++-style comments only
    if(cmt) *cmt = 0; // strip comments by terminating string
    puts(buf);  // Look at the modified string
    char * token;  // strtok uses delimiters to find a token:
    if((token = strtok(buf, delimiters)) != NULL){  // first strtok call
      if(strcspn(token, digits))  // ignore constants
        symbol_table.add_or_count(token);
       // subsequent strtok calls for the same input line:
      while((token = strtok(0, delimiters)) != NULL)
        if(strcspn(token, digits))  // ignore constants
          symbol_table.add_or_count(token);
    }
  }  // print the list in 2 ways, using pointers to members:
  symbol_table.apply(&word::print1);
  symbol_table.apply(&word::print2); putchar('\n');
}  // results are output by the destructor calls
#endif // TEST3


[Example 1: Macro that puts void at the beginning of a line, then
returns to the point where you invoked it.]

macro InsertVoid
      SetMark(0);  /* save our place */
      LeftOfLine;
      InsertText("void ");
      MoveToMark(0);  /* restore the place */
end; /* end of macro InsertVoid */

Alt-V : InsertVoid;   /* bind to key */


[Example 2: Typical collection]

item * i = c.reset();
while(i) {
  // do something
  i = c.next();
}










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.