MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Nested structures

The convenience of taking data and function names out of the global name space extends to structures. You can nest a structure within another structure, and therefore keep associated elements together. The declaration syntax is what you would expect, as you can see in the following structure, which implements a push-down stack as a very simple linked list so it “never” runs out of memory:

//: C04:Nested.h
// Nested struct in linked list
#ifndef NESTED_H
#define NESTED_H

struct Stack {
  struct Link {
    void* data;
    Link* next;
    void initialize(void* Data, Link* Next);
  } * head;
  void initialize();
  void push(void* Data);
  void* peek();
  void* pop();
  void cleanup();
};
#endif // NESTED_H ///:~ 

The nested struct is called Link, and it contains a pointer to the next Link in the list and a pointer to the data stored in the Link. If the next pointer is zero, it means you’re at the end of the list.

Notice that the head pointer is defined right after the declaration for struct Link , instead of a separate definition Link* head . This is a syntax that came from C, but it emphasizes the importance of the semicolon after the structure declaration – the semicolon indicates the end of the list of definitions of that structure type. (Usually the list is empty.)

The nested structure has its own initialize( ) function, like all the structures presented so far, to ensure proper initialization. Stack has both an initialize( ) and cleanup( ) function, as well as push( ), which takes a pointer to the data you wish to store (assumed to have been allocated on the heap), and pop( ), which returns the data pointer from the top of the Stack and removes the top element. (Notice that you are responsible for destroying the destination of the data pointer.) The peek( ) function also returns the data pointer from the top element, but it leaves the top element on the Stack.

cleanup goes through the Stack and removes each element and frees the data pointer (so it must be on the heap).

Here are the definitions for the member functions:

//: C04:Nested.cpp {O}
// Linked list with nesting
#include <cstdlib>
#include "../require.h"
#include "Nested.h"
using namespace std;

void Stack::Link::initialize(
    void* Data, Link* Next) {
  data = Data;
  next = Next;
}

void Stack::initialize() { head = 0; }

void Stack::push(void* Data) {
  Link* newLink = (Link*)malloc(sizeof(Link));
  require(newLink != 0);
  newLink->initialize(Data, head);
  head = newLink;
}

void* Stack::peek() { return head->data; }

void* Stack::pop() {
  if(head == 0) return 0;
  void* result = head->data;
  Link* oldHead = head;
  head = head->next;
  free(oldHead);
  return result;
}

void Stack::cleanup() {
  Link* cursor = head;
  while(head) {
    cursor = cursor->next;
    free(head->data); // Assumes a malloc!
    free(head);
    head = cursor;
  }
} ///:~ 

The first definition is particularly interesting because it shows you how to define a member of a nested structure. You simply use the scope resolution operator a second time, to specify the name of the enclosing struct. The Stack::Link::initialize( ) function takes the arguments and assigns them to its members. Although you can certainly do these things by hand quite easily, you’ll see a different form of this function in the future, so it will make much more sense.

The Stack::initialize( ) function sets head to zero, so the object knows it has an empty list.

Stack::push( ) takes the argument, a pointer to the variable you want to keep track of using the Stack, and pushes it on the Stack. First, it uses malloc( ) to allocate storage for the Link it will insert at the top. Then it calls the initialize( ) function to assign the appropriate values to the members of the Link. Notice that the next pointer is assigned to the current head; then head is assigned to the new Link pointer. This effectively pushes the Link in at the top of the list.

Stack::pop( ) stores the data pointer at the current top of the Stack; then it moves the head pointer down and deletes the old top of the Stack. Stack::cleanup( ) creates a cursor to move through the Stack and free( ) both the data in each link and the link itself.

Here’s an example to test the Stack:

//: C04:NestTest.cpp
//{L} Nested
//{T} NestTest.cpp
// Test of nested linked list
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "../require.h"
#include "Nested.h"
using namespace std;

int main(int argc, char* argv[]) {
  Stack textlines;
  FILE* file;
  char* s;
  #define BUFSIZE 100
  char buf[BUFSIZE];
  requireArgs(argc, 1); // File name is argument
  textlines.initialize();
  file = fopen(argv[1], "r");
  require(file != 0);
  // Read file and store lines in the Stack:
  while(fgets(buf, BUFSIZE, file)) {
    char* string =(char*)malloc(strlen(buf)+1);
    require(string != 0);
    strcpy(string, buf);
    textlines.push(string);
  }
  // Pop the lines from the Stack and print them:
  while((s = (char*)textlines.pop()) != 0) {
    printf("%s", s); free(s); }
  textlines.cleanup();
} ///:~ 

This is very similar to the earlier example, but it pushes the lines on the Stack and then pops them off, which results in the file being printed out in reverse order. In addition, the file name is taken from the command line.

Global scope resolution

The scope resolution operator gets you out of situations where the name the compiler chooses by default (the “nearest” name) isn’t what you want. For example, suppose you have a structure with a local identifier A, and you want to select a global identifier A from inside a member function. The compiler would default to choosing the local one, so you must tell it to do otherwise. When you want to specify a global name using scope resolution, you use the operator with nothing in front of it. Here’s an example that shows global scope resolution for both a variable and a function:

//: C04:Scoperes.cpp {O}
// Global scope resolution
int a;
void f() {}

struct S {
  int a;
  void f();
};

void S::f() {
  ::f();  // Would be recursive otherwise!
  ::a++;  // Select the global a
  a--;    // The a at struct scope
}
///:~

Without scope resolution in S::f( ), the compiler would default to selecting the member versions of f( ) and A.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru