Checking the Inventory

That concludes our tour of the HomeUtility namespace. Now it's time to look at the changes to the next class, HomeInventory. We'll start with the latest version of the header file, hmin8.cpp, shown in Figure 13.19.

Figure 13.19. The latest header file for the HomeInventory class (codehmin8.h)
//hmin8.h
class HomeInventory
{
public:
  HomeInventory();

  short LoadInventory(std::ifstream& is);
  void DumpInventory();
  HomeItem AddItem();
  HomeItem EditItem(short Index);
  Vec<short> LocateItemByDescription(const xstring& Partial);

  Vec<short> LocateItemByCategory(const xstring& Partial);
  Vec<short> LocateItemByPartialName(const xstring& Partial);
  void PrintNames(std::ostream &os);
  void PrintAll(std::ostream &os);
  void StoreInventory(std::ofstream& ofs);
  void DisplayItem(short Index);

  void SortInventoryByName();
  short GetCount();
  short SelectItemByPartialName(const xstring& Partial);
  short SelectItemFromNameList();
  short SelectItemFromDescriptionList(const xstring& Partial);
  short SelectItemFromCategoryList(const xstring& Partial);
  void DeleteItem(short Index);

private:
   Vec<HomeItem> m_Home;
};

As you will see if you compare this version of the HomeInventory class interface to the previous one we examined (hmin6.h in Figure 12.23), I've deleted three functions from this interface — namely, FindItemByDescription, FindItemByName, and LocateItemByName. The first of these is no longer used in the application program, which instead uses the logically equivalent LocateItemByDescription. The other two functions are no longer necessary because they have been superseded by the new LocateItemByPartialName, which can do everything that the old functions could do and a lot more besides.

This new version of the HomeInventory class also includes changes to existing functions. Let's take them in order of their appearance in the header file, starting with the LoadInventory function. The only difference between this version and the previous one is that the new version sorts the inventory by calling the new SortInventoryByName function after loading it. I'll provide a brief explanation of how the sort function works when we get to it. I haven't bothered to reproduce the LoadInventory function here just to show you the one added line.

The next function that was changed is the AddItem function, whose new implementation is shown in Figure 13.20.

Figure 13.20. The latest version of HomeInventory::AddItem (from codehmin8.cpp)
HomeItem HomeInventory::AddItem()
{
 HomeItem TempItem = HomeItem::NewItem();

 if (TempItem.IsNull())
   return TempItem;

 short OldCount = m_Home.size();

 m_Home.resize(OldCount + 1);

 m_Home[OldCount] = TempItem;

 SortInventoryByName();

 return TempItem;
}

As you can see, this version of the function checks whether the newly created item is null, using the new IsNull member function of the HomeItem class. If that turns out to be the case, it returns that null item to the calling function rather than adding it to the inventory. This new version also sorts the inventory after adding an item, just as the new version of the LoadInventory function does.

Now we're up to the EditItem function, the new version of which is shown in Figure 13.21.

Figure 13.21. The latest version of HomeInventory::EditItem (from codehmin8.cpp)
HomeItem HomeInventory::EditItem(short Index)
{
 bool NameChanged = false;

 HomeItem TempItem = m_Home[Index];

 TempItem.Edit();

 if (TempItem.GetName() != m_Home[Index].GetName())
   NameChanged = true;

  m_Home[Index] = TempItem;

  if (NameChanged)
    SortInventoryByName();

  return TempItem;
}

The main difference between this version of EditItem and the previous version is that this one checks to see whether the name of the item has changed. If so, EditItem calls the SortInventoryByName function to ensure that the inventory list is still sorted by the names of the items.

The next function we'll examine is LocateItemByDescription, whose new implementation is shown in Figure 13.22.

Figure 13.22. The latest version of HomeInventory::LocateItemByDescription (from codehmin8.cpp)
Vec<short> HomeInventory::LocateItemByDescription(
 const xstring& Partial)
{
 short ItemCount = m_Home.size();
 xstring Description;
 short FoundCount = 0;

 for (short i = 0; i < ItemCount; i ++)
   {
   Description = m_Home[i].GetDescription();
   if (Description.find_nocase(Partial) >= 0)
     FoundCount ++;
   }

 Vec<short> Found(FoundCount);

 FoundCount = 0;

 for (short i = 0; i < ItemCount; i ++)
   {
   Description = m_Home[i].GetDescription();
   if (Description.find_nocase(Partial) >= 0)
     Found[FoundCount++] = i;
   }

 return Found;
}

This function is quite different from its previous incarnation; even its interface has changed. That's because it now locates all the items that match the description specified in its argument, not just the first one. Therefore, it must return a Vec of indexes rather than only one. Also, because we don't know how many items will be found before we look through the list, we don't know how large the result Vec will be on our first pass. I've solved that by using two passes, with the first pass devoted to finding the number of matching items and the second pass devoted to storing the indexes of those items in the result Vec. One other construct that we haven't seen before is the use of the post-increment operator ++ inside another expression, in the statement Found[FoundCount++] = i;. When this operator is used inside another expression, the value it returns is the pre-incremented value of the variable being incremented. In this case, the value of the expression FoundCount++ is the value that the variable FoundCount had before being incremented. After that value is used, the variable is incremented so that it will be greater by one the next time it is referred to.

Besides modifying the previously noted functions, I've also added quite a few functions to this interface to implement all the new facilities this new version of the program provides. Let's take them one at a time, starting with LocateItemByCategory, which is shown in Figure 13.23.

Figure 13.23. HomeInventory::LocateItemByCategory (from codehmin8.cpp)
Vec<short> HomeInventory::LocateItemByCategory(
 const xstring& Partial)
{
 short ItemCount = m_Home.size();
 xstring Category;
 short FoundCount = 0;

 for (short i = 0; i < ItemCount; i ++)
   {
   Category = m_Home[i].GetCategory();
   if (Category.find_nocase(Partial) >= 0)
     FoundCount ++;
   }

 Vec<short> Found(FoundCount);

 FoundCount = 0;

 for (short i = 0; i < ItemCount; i ++)
   {
   Category = m_Home[i].GetCategory();
   if (Category.find_nocase(Partial) >= 0)
     Found[FoundCount++] = i;
   }

 return Found;
}

As you can see, this is almost identical to the function we've just examined, LocateItemByDescription. The only difference is that we're searching for items whose category matches the user's specification rather than items whose description matches that specification.

I'm not going to waste space by reproducing the code for the LocateItemByPartialName function, which is again almost identical to the two functions we've just looked at. The difference, of course, is that the field it examines for a match is the item's name rather than its description or category.

The next function we will examine is PrintNames, which is shown in Figure 13.24.

Figure 13.24. The HomeInventory::PrintNames function (from codehmin8.cpp)
void HomeInventory::PrintNames(ostream& os)
{
 short ItemCount = m_Home.size();

 for (short i = 0; i < ItemCount; i ++)
   {
   os << m_Home[i].GetName() << endl;
   }

 os << 'f' << endl;
 os.flush();
}

This function merely steps through all the items in the inventory and sends the name of each one to the output stream. One minor point of interest is that to ensure that any further data sent to the printer starts on a new page, I've added a “form-feed” character, represented as 'f', to the end of the output data. After sending that character to the printer, the function ends with a call to the flush function of the ostream object we are sending the data to.

Susan had a couple of questions about this function.

Susan: What is a form-feed?

Steve: It is a character that makes the printer go to a new page. It's called that because years ago printers used continuous-form paper. When you finished printing on one form, you had to send a “form-feed” character to the printer so that it would advance the paper to the beginning of the next form. Today, most printers use cut-sheet paper, but the name has stuck.

Susan: How do we know that the form-feed character has been sent to the printer? Isn't it buffered?

Steve: That's exactly why we have to call the flush function, which ensures that the form-feed has actually been sent to the printer.

The next function is PrintAll. This, as shown in Figure 13.25, is exactly like the previous function, except that it displays all the data for each item rather than just its name.

Figure 13.25. The HomeInventory::PrintAll function (from codehmin8.cpp)
void HomeInventory::PrintAll(ostream& os)
{
 short ItemCount = m_Home.size();

 for (short i = 0; i < ItemCount; i ++)
   {
   os << m_Home[i] << endl;
   }

 os << 'f' << endl;
 os.flush();
}

Now we're up to the StoreInventory function, whose code is shown in Figure 13.26.

Figure 13.26. The HomeInventory::StoreInventory function (from codehmin8.cpp)
void HomeInventory::StoreInventory(ofstream& ofs)
{
 short i;
 short ElementCount = m_Home.size();

 ofs << ElementCount << endl << endl;

 for (i = 0; i < ElementCount; i ++)
   {
   ofs << m_Home[i];
   ofs << endl;
   }
}

As you can see from the code in this function, it is almost identical to the code for PrintAll. The main differences are:

  1. StoreInventory writes the number of items to the file before starting to write the items (so that we can tell how many items are in the file when we read it back later).

  2. It doesn't write a form-feed character to the file after all the items are written because we aren't printing the information.

The similarity between this function and PrintAll shouldn't come as too much of a surprise. After all, storing the inventory data is almost the same as printing it out; both of these operations take data currently stored in objects in memory and transfer it to an output device. The iostream classes are designed to allow us to concentrate on the input or output task to be performed rather than on the details of the output device on which the data is to be written, so the operations needed to write data to a file can be very similar to the operations needed to write data to the printer.

Susan had some questions about this function.

Susan: What is ofs?

Steve: It stands for “output file stream”, because we are writing the data for the items to a file via an ofstream object.

Susan: Why is it good that writing data to a file is like writing data to the printer?

Steve: This characteristic of C++, called device independence, makes it easier to write programs that use a number of different types of output (or input) device, as they all look more or less the same. Having to treat every device differently is a major annoyance to the programmer in languages that don't support device independence.

By the way, we've also made use of this when reading data from either cin or another input stream attached to a file.

Now let's take a look at the next function, DisplayItem, whose code is shown in Figure 13.27.

Figure 13.27. The HomeInventory::DisplayItem function (from codehmin8.cpp)
void HomeInventory::DisplayItem(short Index)
{
 m_Home[Index].FormattedDisplay(cout);
}

This is quite a simple function, as it calls the FormattedDisplay function of the HomeItem class to do all the work of displaying the data for a particular item in the inventory. As you can see, this function always writes the data to the screen.

Sorting the Inventory

The next function we will look at is SortInventoryByName, whose code is shown in Figure 13.28.

Figure 13.28. The HomeInventory::SortInventoryByName function (from codehmin8.cpp)
void HomeInventory::SortInventoryByName()
{
 short ItemCount = m_Home.size();
 Vec<HomeItem> m_HomeTemp = m_Home;
 Vec<xstring> Name(ItemCount);
 xstring HighestName = "zzzzzzzz";
 xstring FirstName;
 short FirstIndex;

 for (int i = 0; i < ItemCount; i ++)
     Name[i] = m_Home[i].GetName();

 for (int i = 0; i < ItemCount; i ++)
     {
     FirstName = HighestName;
     FirstIndex = 0;
     for (int k = 0; k < ItemCount; k ++)
         {
         if (Name[k].less_nocase(FirstName))
             {
             FirstName = Name[k];
             FirstIndex = k;
             }
      }
     m_HomeTemp[i] = m_Home[FirstIndex];
     Name[FirstIndex] = HighestName;
     }

  m_Home = m_HomeTemp;
}

I won't go into detail on the “selection sort” algorithm used in this function because I've already explained it in gory detail in Chapters 4 and 8. The only difference between this implementation and those previous versions is that here we're using the less_nocase function rather than operator < to compare the xstring variables so that we can sort without regard to case. Briefly, the basic idea of this algorithm is that we go through the inventory looking for the item that has the “lowest” name (i.e., the name that would be earliest in the dictionary). When we find it, we copy it to an output list, and then mark it so that we won't pick it again. Then we repeat this process for each item in the original list of names. This is not a particularly efficient sorting algorithm, but it is sufficient for our purposes here.

The next function, GetCount, is extremely simple. Its sole purpose is to return the number of items in the inventory so that the main program can display this information on the screen, and its implementation consists of returning the value obtained from the size member function of the inventory object. Therefore, I won't waste space reproducing it here.

The next function in the header file, SelectItemByPartialName, is more interesting. Take a look at its implementation, which is shown in Figure 13.29.

Figure 13.29. The HomeInventory::SelectItemByPartialName function (from codehmin8.cpp)
short HomeInventory::SelectItemByPartialName(
const xstring& Partial)
{
 Vec<short> Found = LocateItemByPartialName(Partial);

 Vec<xstring> Name(Found.size());

 for (unsigned i = 0; i < Found.size(); i ++)
    Name[i] = m_Home[Found[i]].GetName();

  short Result = HomeUtility::SelectItem(Found,Name) - 1;

  return Result;
}

This function starts by using the LocateItemByPartialName function to get a list of all the items whose names match the xstring specified by the calling function in the argument called Partial (the xstring the user typed to select the items to be listed). Once LocateItemByPartialName returns the Vec of matching item indexes, SelectItemByPartialName continues by extracting the names of those items and putting them in another Vec called Name. Once the names and indexes have been gathered, we're ready to call HomeUtility::SelectItem, which will take care of the actual user interaction needed to find out which item the user really wants to edit. The result of the SelectItem function is either 0 (meaning the user didn't select anything) or an item number, which starts at 1; however, the result of the SelectItemByPartialName function is an index into the inventory list, which is zero-based, as usual in C++. Therefore, we have to subtract 1 from the result of the SelectItem function before returning it as the index into the inventory list.[12]

[12] If SelectItem returns the value 0, then SelectItemByPartialName will return -1, which will tell the calling function that nothing was selected.

By this point, Susan had become absorbed in the role of software developer, if the following exchange is any indication:

Susan: Why are we coddling the users? Let them start counting at 0 like we have to.

Steve: The users are our customers, and they will be a lot happier (and likely to buy more products from us) if we treat them well.

But if you keep that attitude, you may very well qualify to work at certain software companies (whose names I won't mention to avoid the likelihood of lawsuits)!

The next function we'll look at is SelectItemFromNameList, whose code is shown in Figure 13.30.

Figure 13.30. The HomeInventory::SelectItemFromNameList function (from codehmin8.cpp)
short HomeInventory::SelectItemFromNameList()
{
 short ItemCount = m_Home.size();

 Vec<short> Found(ItemCount);

 for (int i = 0; i < ItemCount; i ++)
     Found[i] = i;

 Vec<xstring> Name(Found.size());

 for (unsigned i = 0; i < Found.size(); i ++)
    Name[i] = m_Home[i].GetName();

 short Result = HomeUtility::SelectItem(Found,Name) - 1;

 return Result;
}

This is very similar to the previous function, except that it allows the user to choose from the entire inventory, as there is no selection expression to reduce the number of items to be displayed. Therefore, instead of calling a function to determine which items should be included in the list that the user will pick from, this function makes a list of all of the indexes and item names in the inventory, then calls the SelectItem function to allow the user to pick an item from the whole inventory list.

The next member function listed in the hmin8.h header file is SelectItemFromDescriptionList. I won't reproduce it here because it is virtually identical to the SelectItemByPartialName function, except of course that it uses the description field rather than the item name field to determine which items will end up in the list the user selects from. This means that it calls LocateItemByDescription to find items, rather than LocateItemByPartialName, which the SelectItemByPartialName function uses for that same purpose.

Selecting by Category

The next function in the header file, SelectItemFromCategoryList (Figure 13.31), is more interesting, if only because it does some relatively fancy formatting to get its display to line up properly, using concatenation to append one xstring to another.

Figure 13.31. The HomeInventory::SelectItemFromCategoryList function (from codehmin8.cpp)
short HomeInventory::SelectItemFromCategoryList(
 const xstring& Partial)
{
 Vec<short> Found = LocateItemByCategory(Partial);

 Vec<xstring> Name(Found.size());
 Vec<xstring> Category(Found.size());
 xstring Padding;
 unsigned PaddingLength;
 const unsigned ItemNumberLength = 7;

 unsigned MaxLength = 0;

 for (unsigned i = 0; i < Found.size(); i ++)
   {
   Category[i] = m_Home[Found[i]].GetCategory();
   Name[i] = m_Home[Found[i]].GetName();
   if (Name[i].size() > MaxLength)
      MaxLength = Name[i].size();
     }

 for (unsigned i = 0; i < Found.size(); i ++)
   {
   PaddingLength = MaxLength - Name[i].size();
   Padding = xstring(PaddingLength);
   Name[i] = Name[i] + Padding + "    " + Category[i];
   }

  MaxLength += ItemNumberLength;
  xstring Heading = "Item #  Name";
  unsigned HeadingLength = Heading.size();
  if (MaxLength > HeadingLength)
    PaddingLength = MaxLength - HeadingLength;
  else
   PaddingLength = 0;
  Padding = xstring(PaddingLength);
  Heading = Heading + Padding + "     Category";
  cout << Heading << endl << endl;

  short Result = HomeUtility::SelectItem(Found,Name) - 1;

  return Result;
}

This function starts out pretty much like the other “select” functions — calling a “locate” function to gather indexes of items that match a particular criterion, which in this case is the category of the item. However, once these indexes have been gathered, instead of simply collecting the names of the items into a Vec, we also must determine the length of the longest name, so that when we display the category of each item after its name, the category names will line up evenly. To make this possible, we have to “pad” the shorter names to the same length as the longest name. The code to do this is in the two lines of the for loop that gathers the names and categories:

if (Name[i].GetLength() > MaxLength)
      MaxLength = Name[i].GetLength();

If the current name is longer than the longest name so far, we update that MaxLength variable to the length of the current name. By the time we reach the end of the list of names, MaxLength will be the length of the longest name.

In the next for loop, we calculate the amount of padding each name will require, based on the difference between its length and the length of the longest name. Then we use the xstring(unsigned) constructor to create a xstring consisting of the number of spaces that will make the current name as long as the longest name. As soon as we have done that, we add the padding and the category name to the name of each object. At the end of this loop, we are finished with the preparation of the data that will be used in the SelectItem function.

However, we still have more work to do before we call that function because we want to display a heading on the screen to tell the user what he or she is looking at. That's the task of the next section of the code. It starts out by adding the unsigned const value ItemNumberLength to the MaxLength variable to account for the length of the item number field.[13]

[13] I should mention here that it is not a good idea to use “magic” numbers in programs. These are numbers that do not have any obvious relationship to the rest of the code. A good general rule is that numbers other than 0, 1, or other self-evident values should be defined as const or enum values rather than as literal values like '7'. That's why I've defined a const value for the item number length, even though that value is used in only one place.

Next, we start constructing the heading line, starting with the literal value “Item # Name”. To make the category heading line up over the category names in the display, we have to pad the heading line to the length of the longest name, if necessary. This will be needed if the heading is shorter than the length of the longest name plus the allowance of ItemNumberLength characters for the item number.[14] Once we have calculated the length of that padding (if any), we construct it and add it to the end of the heading so far. Then we add the “Category” heading to the heading line. Now the heading is finished, so we write it to cout. Finally, we call SelectItem to allow the user to select an item from our nicely formatted list, and return the result of that call to the calling function.

[14] By the way, neglecting the possibility that the heading is already long enough caused a “virtual memory exceeded” error message in an earlier version of this program. The problem was that the length of the padding was calculated as a negative value. However, the argument to operator new is unsigned. Therefore, when I asked it to allocate (for example) -3 characters of memory, it tried to give me approximately 4 billion bytes. Fortunately, that exceeds the maximum amount my operating system can handle, so I got an error message.

This function was the stimulus for a discussion of software development issues with Susan.

Susan: That sure is a lot of work just to handle item names of different lengths. Wouldn't it be simpler to assume a certain maximum size?

Steve: We'd still have to pad all the names before tacking the category on; the only simplification would be in the creation of the header, so it wouldn't really make the function much simpler.

Susan: What would happen if we had such a long name or category that the line wouldn't fit on the screen?

Steve: That's a very good question. In that case, the display would be messed up. However, I don't think that's very likely because the user probably wouldn't want to type in such a long name or category.

Susan: Okay. Now I have another question. If we were printing a report of all these items and categories, would each page line up differently from the others if it had a longer or shorter name length?

Steve: Well, so far we haven't implemented a report like that. However, if we did, each page would line up the same on any particular report because we go through the whole list to find the longest name. On the other hand, if we ran the report several times with different data, it is possible that the longest name would be of a different length in each report, so the columns wouldn't line up the same between reports.

Susan: So there really isn't any cut and dried way to make these decisions?

Steve: No, I'm afraid not. That's why they pay me the (relatively) big bucks as a software developer. I have to laugh whenever I see ads for “automatic bug-finder” software, especially when it claims to be able to find design flaws automatically. How does it know what problem I'm trying to solve?

The final function in this class is DeleteItem, whose code is shown in Figure 13.32.

Figure 13.32. The HomeInventory::DeleteItem function (from codehmin8.cpp)
void HomeInventory::DeleteItem(short Index)
{
 short ItemCount = m_Home.size();

 for (short i = Index; i < ItemCount-1; i ++)
   m_Home[i] = m_Home[i+1];

 m_Home.resize(ItemCount-1);
}

This is another simple function. Starting at the item to be deleted, it moves all of the items after that point one position closer to the beginning of the inventory list and then reduces the size of the list by one. This effectively eliminates the selected item from the inventory.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset