ADTs

Steven J Zeil

Contents:

1 Abstraction

In general, abstraction is a creative process of focusing attention on the main problems by ignoring lower-level details.

In programming, we encounter two particular kinds of abstraction:

1.1 Procedural Abstraction

A procedural abstraction is a mental model of what we want a subprogram to do (but not how to do it).

Example: if you wanted to compute the length of the a hypotenuse of a right triangle, you might write something like

double hypotenuse = sqrt(side1*side1 + side2*side2);

We can write this, understanding that the sqrt function is supposed to compute a square root, even if we have no idea how that square root actually gets computed.

When we start actually writing the code, we implement a procedural abstraction by

In practice, there may be many algorithms to achieve the same abstraction, and we use engineering considerations such as speed, memory requirements, and ease of implementation to choose among the possibilities.

For example, the “sqrt” function is probably implemented using a technique completely unrelated to any technique you may have learned in grade school for computing square roots. On many systems. sqrt doesn’t compute a square root at all, but computes a polynomial function that was chosen as a good approximation to the actual square root and that can be evaluated much more quickly than an actual square root. It may then refine the accuracy of that approximation by applying Newton’s method, a technique you may have learned in Calculus.

Does it bother you that sqrt does not actually compute via a square root algorithm? Probably not. It shouldn’t. As long as we trust the results, the method is something we are happy to ignore.

1.2 Data Abstraction

Data abstraction works much the same way.

A data abstraction is a mental model of what can be done to a collection of data. It deliberately excludes details of how to do it.


Example: calendar days

A day (date?) in a calendar denotes a 24-hour period, identified by a specific year, month, and day number.

That’s it. That’s probably all you need to know for you and I to agree that we are talking about a common idea.


 

One of the running examples used throughout this course is the design and implementation of a spreadsheet.

Example: cell names

Every cell in a spreadsheet has a unique name. The name has a column part and a row part.

For example, suppose we have a cell B1 containing the formula 2*A1 and that we copy and paste that cell to position C3. When we look in C3, we would find the pasted formula was 2*B3 adjusted for the number of rows and columns over which we moved the copied value.

But if the cell B1 originally contained the formula 2*A$1, the copied formula would be 2*B$1. The $ indicates that we are fixing the column indicator during copies. Similarly, if the cell B1 originally contained the formula 2*$A$1, the copied formula would be 2*$A$1. (If this isn’t clear, fire up a spreadsheet and try it. We can’t expect to share mental models (abstractions) if we don’t share an experience with the subject domain.)


Example: a book

How to describe a book?


Example: positions within a container

Many of the abstractions that we work with are “containers” of arbitrary numbers of pieces of other data.

Any time you have an ordered sequence of data, you can imagine the need to look through it. That then leads to the concept of a position within that sequence, with notions like

2 Abstract Data Types

Adding Interfaces


Definition of an Abstract Data Type

(traditional): An abstract data type (ADT) is a type name and a list of operations on that type.

It’s convenient, for the purpose of this course, to modify this definition just slightly:

Definition (alternate): An abstract data type (ADT) is a type name and a list of members (data or function) on that type.

This change is not really all that significant. Traditionally, a data member X is modeled as a pair of getX() and putX(x) (or ’setX(x)) functions. But in practice, we will allow ADTs to include data members in their specification. This definition may make it a bit clearer that an ADT corresponds, more or less, to the public portion of a typical class.

In either case, when we talk about listing the members, this includes giving their names and their data types (for functions, their return types and the data types of their parameters).

If you search the web for the phrase “abstract data type”, you’ll find lots of references to stacks, queues, etc. - the “classic” data structures. Certainly, these are ADTs. But, just as with the abstractions introduced earlier, each application domain has certain characteristic or natural abstractions that may also need programming interfaces.


ADT Members: attributes and operations

Commonly divided into

2.1 Examples

2.1.1 Calendar Days

Nothing in the definition of ADT that says that the interface has to be written out in a programming language.

 

UML diagrams present classes as a 3-part box: name, attributes, & operations

2.1.2 Calendar Days: Alternative

But we can use a more programming-style interface:

 

class Day {
public:
   // Attributes
   int getDay();
   void setDay (int);
   int getMonth();
   void setMonth(int);
   int getYear();
   void setYear(int);
   
   // Operations
   Day operator+ (int numDays);
   int operator- (Day);
   bool operator< (Day);
   bool operator== (Day);
     ⋮

Notations

 

class Day {
public:
   // Attributes
   int getDay();
   void setDay (int);
   int getMonth();
   void setMonth(int);
   int getYear();
   void setYear(int);
   
   // Operations
   Day operator+ (int numDays);
   int operator- (Day);
   bool operator< (Day);
   bool operator== (Day);
     ⋮

2.1.3 Cell Names

Here is a possible interface for our cell name abstraction.

 

cellnameInterface.h

class CellName
{
public:
  CellName (std::string column, int row,
            bool fixTheColumn = false,
            bool fixTheRow=false);
  //pre: column.size() > 0 && all characters in column are alphabetic
  //     row > 0

  CellName (std::string cellname);
  //pre: exists j, 0<=j<cellname.size()-1, 
  //        cellname.substr(0,j) is all alphabetic (except for a
  //             possible cellname[0]=='$')
  //        && cellname.substr(j) is all numeric (except for a
  //             possible cellname[j]=='$') with at least one non-zero
  //             digit

  CellName (unsigned columnNumber = 0, unsigned rowNumber = 0,
            bool fixTheColumn = false,
            bool fixTheRow=false);

  std::string toString() const;
  // render the entire CellName as a string

  // Get components in spreadsheet notation
  std::string getColumn() const;
  int getRow() const;

  bool isRowFixed() const;
  bool isColumnFixed() const;


  // Get components as integer indices in range 0..
  int getColumnNumber() const;
  int getRowNumber() const;


  bool operator== (const CellName& r) const
     ⋮
private:
     ⋮

Arguably, the diagram presents much the same information as the code

2.1.4 Example: A Book

If we were to try to capture our book abstraction (concentrating on the metadata), we might come up with something like:

bookAbstraction0.h

class Book {
public:
  Book (Author)                 // for books with single authors
  Book (Author[], int nAuthors) // for books with multiple authors

  std::string getTitle() const;
  void putTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getIsBN() const;
  void putISBN(std::string id);

  Publisher getPublisher() const;
  void putPublisher(const Publisher& publ);

  AuthorPosition begin();
  AuthorPosition end();

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:
  ⋮
};

2.1.5 Example: Positions within a Container

Coming up with a good interface for our position abstraction is a problem that has challenged many an ADT designer.

bookNumericPositions.h
class Book {
public:
  Book (Author)                 // for books with single authors
  Book (Author[], int nAuthors) // for books with multiple authors

  std::string getTitle() const;
  void putTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getIsBN() const;
  void putISBN(std::string id);

  Publisher getPublisher() const;
  void putPublisher(const Publisher& publ);

  typedef int AuthorPosition;
  <+1>Author getAuthor (AuthorPosition authorNum) const;  <-1>

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:
  ⋮
};

Iterators

The solution adapted by the C++ community is to have every ADT that is a “container” of sequences of other data to provide a special type for positions within that sequence.



A Possible Position Interface

In theory, we could satisfy this requirement with an ADT like this:

authorPosition0.h

class AuthorPosition {
public:
   AuthorPosition();

   // get data at this position
   Author getData() const;

   // get the position just after this one
   AuthorPosition next() const;

   // Is this the same position as pos?
   bool operator== (const AuthorPosition& pos) const;
   bool operator!= (const AuthorPosition& pos) const;

};

which in turn would allow us to access authors like this:

void listAllAuthors(Book& b)
{
   for (AuthorPosition p = b.begin(); p != b.end(); 
        p = p.next())
     cout << "author: " << p.getData() << endl;
}


The Iterator ADT

For historical reasons (and brevity), however, C++ programmers use overloaded operators for the getData() and next() operations:

authorPosition1.h

class AuthorPosition {
public:
   AuthorPosition();

   // get data at this position
   Author operator*() const;

   // get a data/function member at this position
   Author* operator->() const;

   // move forward to the position just after this one
   AuthorPosition operator++();

   // Is this the same position as pos?
   bool operator== (const AuthorPosition& pos) const;
   bool operator!= (const AuthorPosition& pos) const;

};

so that code to access authors would look like this:

void listAllAuthors(Book& b)
{
   for (AuthorPosition p = b.begin(); p != b.end(); 
        ++p)
     cout << "author: " << *p << endl;
}

This ADT for positions is called an iterator (because it lets us iterate over a collection of data).

2.2 Design Patterns


Iterator as a Design Pattern

 
The idea of an iterator is an instance of what we call a design pattern:


Pattern, not ADT

In C++, our application code does not actually work with an actual ADT named “Iterator”.


Realizing a Design Pattern

 

3 ADTs as contracts


ADTs as contracts

An ADT represents a contract between the ADT developer and the users (application programmers).

The Contract


Why the Contract

What do we gain by holding ourselves to this contract?

3.1 Information Hiding


Information Hiding

Every design can be viewed as a collection of “design decisions”.


Encapsulation

Although ADTs can be designed without language support, they rely on programmers’ self-discipline for enforcement of information hiding.

Encapsulation is the enforcement of information hiding by programming language constructs.

4 ADT Implementations


ADT Implementations

An ADT is implemented by supplying

We sometimes refer to the ADT itself as the ADT specification or the ADT interface, to distinguish it from the code of the ADT implementation.

In C++, implementation is generally done using a C++ class.

4.1 Examples

4.2 Calendar Day Implementations

As ADT designer, I might consider two possible data structures:

v1-day.h
class Day
{
   /**
      Represents a day with a given year, month, and day
      of the Gregorian calendar. The Gregorian calendar
      replaced the Julian calendar beginning on
      October 15, 1582
   */

public:

  Day(int aYear, int aMonth, int aDate);


   /**
      Returns the year of this day
      @return the year
   */
  int getYear() const;


  /**
     Returns the month of this day
     @return the month
  */
  int getMonth() const;

  /**
     Returns the day of the month of this day
     @return the day of the month
  */
  int getDate() const;


  /**
     Returns a day that is a certain number of days away from
     this day
     @param n the number of days, can be negative
     @return a day that is n days away from this one
  */
  Day addDays(int n) const;


  /**
     Returns the number of days between this day and another
     day
     @param other the other day
     @return the number of days that this day is away from 
     the other (>0 if this day comes later)
  */
  int daysFrom(Day other) const;


  enum Months {JANUARY = 1,
	       FEBRUARY = 2,
	       MARCH = 3,
	       APRIL = 4,
	       MAY = 5,
	       JUNE = 6,
	       JULY = 7,
	       AUGUST = 8,
	       SEPTEMBER = 9,
	       OCTOBER = 10,
	       NOVEMBER = 11,
	       DECEMBER = 12};

private:

  /**
     Compares this day with another day.
     @param other the other day
     @return a positive number if this day comes after the
     other day, a negative number if this day comes before
     the other day, and zero if the days are the same
  */
  int compareTo(Day other) const;

  
  /**
     Computes the next day.
     @return the day following this day
  */
  Day nextDay() const;

  /**
     Computes the previous day.
     @return the day preceding this day
  */
  Day previousDay() const;

  /**
     Gets the days in a given month
     @param y the year
     @param m the month
     @return the last day in the given month
   */
  static int daysPerMonth(int y, int m);

  /**
     Tests if a year is a leap year
     @param y the year
     @return true if y is a leap year
  */
  static bool isLeapYear(int y);
  
  int year;
  int month;
  int date;

  static const int DAYS_PER_MONTH [12];
  // = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

  static const int GREGORIAN_START_YEAR = 1582;
  static const int GREGORIAN_START_MONTH = 10;
  static const int GREGORIAN_START_DAY = 15;
  static const int JULIAN_END_DAY = 4;
  
};





v1-day.cpp
/**
   Day class

   copyright Steven J Zeil, 2003, based upon
     Day.java from Cay Horstmann's "Object-Oriented Design & Patterns"
**/

#include "day.h"

using namespace std;



 const int Day::DAYS_PER_MONTH [12]
  = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };




/**
   Constructs a day with a given year, month, and day
   of the Julian/Gregorian calendar. The Julian calendar
   is used for all days before October 15, 1582
   @param aYear a year != 0
   @param aMonth a month between 1 and 12
   @param aDate a date between 1 and 31
*/


Day::Day(int aYear, int aMonth, int aDate)
{
  year = aYear;
  month = aMonth;
  date = aDate;
}

/**
   Returns the year of this day
   @return the year
*/
int Day::getYear() const
{
  return year;
}

/**
   Returns the month of this day
   @return the month
*/
int Day::getMonth() const
{
  return month;
}

/**
   Returns the day of the month of this day
   @return the day of the month
*/
int Day::getDate() const
{
  return date;
}

/**
   Returns a day that is a certain number of days away from
   this day
   @param n the number of days, can be negative
   @return a day that is n days away from this one
*/
Day Day::addDays(int n) const
{
  Day result = *this;
  while (n > 0)
    {
      result = result.nextDay();
      n--;
    }
  while (n < 0)
    {
      result = result.previousDay();
      n++;
    }
  return result;
}

/**
   Returns the number of days between this day and another
   day
   @param other the other day
   @return the number of days that this day is away from 
   the other (>0 if this day comes later)
*/
int Day::daysFrom(Day other) const
{
  int n = 0;
  Day d = *this;
  while (d.compareTo(other) > 0)
    {
      d = d.previousDay();
      n++;
    }
  while (d.compareTo(other) < 0)
    {
      d = d.nextDay();
      n--;
    }
  return n;
}


/**
   Compares this day with another day.
   @param other the other day
   @return a positive number if this day comes after the
   other day, a negative number if this day comes before
   the other day, and zero if the days are the same
*/
int Day::compareTo(Day other) const
{
  if (year > other.year) return 1;
  if (year < other.year) return -1;
  if (month > other.month) return 1;
  if (month < other.month) return -1;
  return date - other.date;
}

/**
   Computes the next day.
   @return the day following this day
*/
Day Day::nextDay() const
{
  int y = year;
  int m = month;
  int d = date;
  
  if (y == GREGORIAN_START_YEAR
      && m == GREGORIAN_START_MONTH
      && d == JULIAN_END_DAY)
    d = GREGORIAN_START_DAY;
  else if (d < daysPerMonth(y, m))
    d++;
  else
    {
      d = 1;
      m++;
      if (m > DECEMBER) 
	{ 
	  m = JANUARY; 
	  y++; 
	  if (y == 0) y++;
	}
    }
  return Day(y, m, d);
}

/**
   Computes the previous day.
   @return the day preceding this day
*/
Day Day::previousDay() const
{
  int y = year;
  int m = month;
  int d = date;
  
  if (y == GREGORIAN_START_YEAR
      && m == GREGORIAN_START_MONTH
      && d == GREGORIAN_START_DAY)
    d = JULIAN_END_DAY;
  else if (d > 1)
    d--;
  else
    {	
      m--;
      if (m < JANUARY) 
	{             
	  m = DECEMBER; 
	  y--; 
	  if (y == 0) y--;
	}
      d = daysPerMonth(y, m);
    }
  return Day(y, m, d);
}

/**
   Gets the days in a given month
   @param y the year
   @param m the month
   @return the last day in the given month
*/
int Day::daysPerMonth(int y, int m)
{
  int days = DAYS_PER_MONTH[m - 1];
  if (m == FEBRUARY && isLeapYear(y)) 
    days++;
  return days;
}

/**
   Tests if a year is a leap year
   @param y the year
   @return true if y is a leap year
*/
bool Day::isLeapYear(int y)
{
  if (y % 4 != 0) return false;
  if (y < GREGORIAN_START_YEAR) return true;
  return (y % 100 != 0) || (y % 400 == 0);
}






v2-day.h
/**
   Day class

   copyright Steven J Zeil, 2003, based upon
     Day.java from Cay Horstmann's "Object-Oriented Design & Patterns"
**/

class Day
{
   /**
      Constructs a day with a given year, month, and day
      of the Julian/Gregorian calendar. The Julian calendar
      is used for all days before October 15, 1582
      @param aYear a year != 0
      @param aMonth a month between 1 and 12
      @param aDate a date between 1 and 31
   */

public:

  Day(int aYear, int aMonth, int aDate);


   /**
      Returns the year of this day
      @return the year
   */
  int getYear() const;


  /**
     Returns the month of this day
     @return the month
  */
  int getMonth() const;

  /**
     Returns the day of the month of this day
     @return the day of the month
  */
  int getDate() const;


  /**
     Returns a day that is a certain number of days away from
     this day
     @param n the number of days, can be negative
     @return a day that is n days away from this one
  */
  Day addDays(int n) const;


  /**
     Returns the number of days between this day and another
     day
     @param other the other day
     @return the number of days that this day is away from 
     the other (>0 if this day comes later)
  */
  int daysFrom(Day other) const;


  enum Months {JANUARY = 1,
	       FEBRUARY = 2,
	       MARCH = 3,
	       APRIL = 4,
	       MAY = 5,
	       JUNE = 6,
	       JULY = 7,
	       AUGUST = 8,
	       SEPTEMBER = 9,
	       OCTOBER = 10,
	       NOVEMBER = 11,
	       DECEMBER = 12};

private:

  /**
     Computes the Julian day number of the given day.
     @param year a year
     @param month a month
     @param date a date
     @return The Julian day number that begins at noon of 
     the given day
     Positive year signifies CE, negative year BCE. 
     Remember that the year after 1 BCE was 1 CE.
  */
  static int getJulian(int year, int month, int date);


  struct CalDate {
    int year;
    int month;
    int date;
  };

  /**
     Converts a Julian day number to a calendar date.
     
     @param j  the Julian day number
     @return a structure containing the year, month,
       and day of the month.
  */
  static CalDate fromJulian(int j);

  Day (int julianDate);

  
  int julian;
  
};





v2-day.cpp
/**
   Day class

   copyright Steven J Zeil, 2003, based upon
     Day.java from Cay Horstmann's "Object-Oriented Design & Patterns"
**/

#include "day.h"

using namespace std;




/**
   Constructs a day with a given year, month, and day
   of the Julian/Gregorian calendar. The Julian calendar
   is used for all days before October 15, 1582
   @param aYear a year != 0
   @param aMonth a month between 1 and 12
   @param aDate a date between 1 and 31
*/


Day::Day(int aYear, int aMonth, int aDate)
{
  julian = toJulian(aYear, aMonth, aDate); 
}


/**
   Constructs a day with a Julian date.
   @param julianDate a valid Julian date
*/
Day::Day(int julianDate)
  : julian(julianDate)
{
}


/**
   Returns the year of this day
   @return the year
*/
int Day::getYear() const
{
  return fromJulian(julian).year;
}

/**
   Returns the month of this day
   @return the month
*/
int Day::getMonth() const
{
  return fromJulian(julian).month;
}

/**
   Returns the day of the month of this day
   @return the day of the month
*/
int Day::getDate() const
{
  return fromJulian(julian).date;
}

/**
   Returns a day that is a certain number of days away from
   this day
   @param n the number of days, can be negative
   @return a day that is n days away from this one
*/
Day Day::addDays(int n) const
{
  return Day(julian + n);
}

/**
   Returns the number of days between this day and another
   day
   @param other the other day
   @return the number of days that this day is away from 
   the other (>0 if this day comes later)
*/
int Day::daysFrom(Day other) const
{
  return julian - other.julian;
}





/**
   Computes the Julian day number of the given day.
   @param year a year
   @param month a month
   @param date a date
   @return The Julian day number that begins at noon of 
   the given day
   Positive year signifies CE, negative year BCE. 
   Remember that the year after 1 BCE was 1 CE.
   
   A convenient reference point is that May 23, 1968 noon
   is Julian day number 2440000.
   
   Julian day number 0 is a Monday.
   
   This algorithm is from Press et al., Numerical Recipes
   in C, 2nd ed., Cambridge University Press 1992
*/
int Day::toJulian(int year, int month, int date)
{  
  int jy = year;
  if (year < 0) jy++;
  int jm = month;
  if (month > 2) jm++;
  else
    {  
      jy--;
      jm += 13;
    }
  int jul = (int) (int(365.25 * jy) 
		   + int(30.6001*jm) + date + 1720995.0);
  
  int IGREG = 15 + 31*(10+12*1582);
  // Gregorian Calendar adopted Oct. 15, 1582
  
  if (date + 31 * (month + 12 * year) >= IGREG)
    // change over to Gregorian calendar
    {  
      int ja = (int)(0.01 * jy);
      jul += 2 - ja + (int)(0.25 * ja);
    }
  return jul;
}


/**
   Converts a Julian day number to a calendar date.
   
   This algorithm is from Press et al., Numerical Recipes
   in C, 2nd ed., Cambridge University Press 1992
   
   @param j  the Julian day number
   @return an array whose 0 entry is the year, 1 the month,
   and 2 the day of the month.
*/
Day::CalDate Day::fromJulian(int j)
{  
  int ja = j;
  
  int JGREG = 2299161;
  /* the Julian day number of the adoption of the Gregorian
     calendar    
  */   
  
  if (j >= JGREG)
    /* cross-over to Gregorian Calendar produces this 
       correction
    */   
    {  
      int jalpha = (int)(((float)(j - 1867216) - 0.25) 
			 / 36524.25);
      ja += 1 + jalpha - (int)(0.25 * jalpha);
    }
  int jb = ja + 1524;
  int jc = (int)(6680.0 + ((float)(jb-2439870) - 122.1)
		 /365.25);
  int jd = (int)(365 * jc + (0.25 * jc));
  int je = (int)((jb - jd)/30.6001);
  int date = jb - jd - (int)(30.6001 * je);
  int month = je - 1;
  if (month > 12) month -= 12;
  int year = jc - 4715;
  if (month > 2) --year;
  if (year <= 0) --year;
  return (CalDate) { year, month, date };
}





Each approach has pros and cons.

4.3 CellName Implementation

cellnameImpl.cpp

class CellName
{
public:
  CellName (std::string column, int row,
            bool fixTheColumn = false,
            bool fixTheRow=false);
  //pre: column.size() > 0 && all characters in column are alphabetic
  //     row > 0

  CellName (std::string cellname);
  //pre: exists j, 0<=j<cellname.size()-1, 
  //        cellname.substr(0,j) is all alphabetic (except for a
  //             possible cellname[0]=='$')
  //        && cellname.substr(j) is all numeric (except for a
  //             possible cellname[j]=='$') with at least one non-zero
  //             digit

  CellName (unsigned columnNumber = 0, unsigned rowNumber = 0,
            bool fixTheColumn = false,
            bool fixTheRow=false);

  std::string toString() const;
  // render the entire CellName as a string

  // Get components in spreadsheet notation
  std::string getColumn() const;
  int getRow() const;

  bool isRowFixed() const;
  bool isColumnFixed() const;


  // Get components as integer indices in range 0..
  int getColumnNumber() const;
  int getRowNumber() const;


  bool operator== (const CellName& r) const
    {return (columnNumber == r.columnNumber &&
             rowNumber == r.rowNumber &&
             theColIsFixed == r.theColIsFixed &&
             theRowIsFixed == r.theRowIsFixed);}

private:
  ⋮
  int rowNumber;
  bool theRowIsFixed;
  bool theColIsFixed;

  int CellName::alphaToInt (std::string columnIndicator) const;
  std::string CellName::intToAlpha (int columnIndex) const;

};


inline
bool CellName::isRowFixed() const {return theRowIsFixed;}

inline
bool CellName::isColumnFixed() const {return theColIsFixed;}



#endif

There are some options here the have not been explored:

4.4 Book Implementation

We can implement Book in book.h:

book1.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const;
  void setTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getISBN() const;
  void setISBN(std::string id);

  Publisher getPublisher() const;
  void setPublisher(const Publisher& publ);

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  static const int MAXAUTHORS = 12;
  Author authors[MAXAUTHORS];

};

#endif

and in book.cpp:

book1.cpp
#include "book1.h"

  // for books with single authors
Book::Book (Author a)
{
  numAuthors = 1;
  authors[0] = a;
}

// for books with multiple authors
Book::Book (const Author au[], int nAuthors)
{
  numAuthors = nAuthors;
  for (int i = 0; i < nAuthors; ++i)
    {
      authors[i] = au[i];
    }
}

std::string Book::getTitle() const
{
  return title;
}

void Book::setTitle(std::string theTitle)
{
  title = theTitle;
}

int Book::getNumberOfAuthors() const
{
  return numAuthors;
}

std::string Book::getISBN() const
{
  return isbn;
}

void Book::setISBN(std::string id)
{
  isbn = id;
}

Publisher Book::getPublisher() const
{
  return publisher;
}

void Book::setPublisher(const Publisher& publ)
{
  publisher = publ;
}

Book::AuthorPosition Book::begin() const
{
  return authors;
}

Book::AuthorPosition Book::end() const
{
  return authors+numAuthors;
}


void Book::addAuthor (Book::AuthorPosition at, const Author& author)
{
  int i = numAuthors;
  int atk = at - authors;
  while (i >= atk) 
    {
      authors[i+1] = authors[i];
      i--;
    }
  authors[atk] = author;
  ++numAuthors;
}


void Book::removeAuthor (Book::AuthorPosition at)
{
  int atk = at - authors;
  while (atk + 1 < numAuthors)
    {
      authors[atk] = authors[atk + 1];
      ++atk;
    }
  --numAuthors;
}