TOC PREV NEXT INDEX

C++: A Dialog


13.18. 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 (code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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 code\hmin8.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.1
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 code\hmin8.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 code\hmin8.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 ++)
{