Searching for an Item by a Substring

Let's add some capabilities to our home inventory project. First on the list is the ability to find an item by searching for a sequence of chars in its description. Before we see how this is implemented, let's take a look at how it is used. Figure 12.22 shows the new application program that uses this feature.

Figure 12.22. The latest home inventory application program (codehmtst6.cpp)
#include <iostream>
#include <fstream>
#include <string>

#include "Vec.h"
#include "xstring.h"
#include "hmit6.h"
#include "hmin6.h"
using namespace std;

int main()
{
   ifstream HomeInfo("home3.in");
   HomeInventory MyInventory;
   HomeItem TempItem;
   xstring Name;
   xstring Description;

   MyInventory.LoadInventory(HomeInfo);

   TempItem = MyInventory.FindItemByName("Relish");
   cout << endl;

   TempItem.Edit();
   cout << endl;

   TempItem.FormattedDisplay(cout);
   cout << endl;

   cout << "Please enter a search string: ";
   cin >> Description;

   TempItem = MyInventory.FindItemByDescription(Description);

   if (TempItem.IsNull())
     cout << "Sorry, I couldn't locate that item." << endl;
   else
     TempItem.FormattedDisplay(cout);
   cout << endl;

   return 0;
}

This program starts out just as the previous one did, by loading the inventory from the input file, looking up the entry whose name is “Relish”, and displaying it for editing. Once the user has finished editing the entry, we get to the new part: reading a search xstring from the user and searching for that item in the inventory by the new FindItemByDescription member function of HomeInventory. Let's go through the changes needed to implement this new feature, starting with the new version of the HomeInventory interface, shown in Figure 12.23.

Figure 12.23. The latest version of the HomeInventory interface (hmin6.h)
 //hmin6.h

class HomeInventory
{
public:
  HomeInventory();

  short LoadInventory(std::ifstream& is);
  void DumpInventory();
  HomeItem FindItemByName(const xstring& Name);
  HomeItem AddItem();
  short LocateItemByName(const xstring& Name);
  HomeItem EditItem(const xstring& Name);

  HomeItem FindItemByDescription(const xstring& Partial);
  short LocateItemByDescription(const xstring& Partial);

private:
  Vec<HomeItem> m_Home;
};

The only new functions added to this interface since the last version (see Figure 11.26) are the two “ItemByDescription” functions that parallel the “ItemByName” functions we implemented previously. However, there's another modification in this and the other new interface files: I've changed all the value arguments of user-defined types to const references, because passing such arguments by const reference is more efficient than passing them by value but just as safe, since it's impossible to accidentally change the calling function's variables through a const reference. For this reason, this is usually the best method of passing arguments of user-defined types. Variables of native types, on the other hand, are most efficiently passed by value because they do not require copy constructors or other overhead when passed in that way, as objects of user-defined types do.

Susan had a question about passing arguments.

Susan: Why does it matter whether an argument is native or user-defined?

Steve: Remember, every value argument in a function is actually a new variable whose value is copied from the argument passed by the caller. Passing a native variable by value is efficient because it's a simple “bunch of bits”; there's no need to worry about pointers and the like. On the other hand, passing a user-defined variable by value requires a lot more work because the compiler has to call the copy constructor for that type of variable. Therefore, it's best to avoid call-by-value for user-defined types when it isn't necessary.

Now let's take a look at the first of the two new functions, HomeInventory::FindItemByDescription, shown in Figure 12.24.

Figure 12.24. HomeInventory::FindItemByDescription (from codehmin6.cpp)
HomeItem HomeInventory::FindItemByDescription(
 const xstring& Partial)
{
 short i;
 xstring Description;
 bool Found = false;
 short ItemCount = m_Home.size();

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

 if (Found)
  return m_Home[i];

 return HomeItem();
}

This function is very similar to FindItemByName, so I won't go over it in excruciating detail. The main difference between the two is that FindItemByDescription uses the new find_nocase function to locate an occurrence of a search xstring named Partial in the description of each HomeItem object in the m_Home vector. To do this, it retrieves the description from a HomeItem object, using the GetDescription member function, and stores it in an xstring called (imaginatively enough) Description. Then it calls find_nocase to see if there is an occurrence of Partial in the Description xstring. If so, it leaves the loop and returns the object whose description contained the contents of the Partial argument; otherwise, it executes the loop again. This continues until it either finds a match or runs out of items to examine. In the latter case, it returns a null HomeItem to indicate that it couldn't find what the user was looking for.

Susan had a question about why we have to return a null HomeItem in the latter case:

Susan: Why do we have to return anything if we don't find what we're looking for? Why not just return nothing?

Steve: We can't return nothing, because the declaration of our function says that we will return a HomeItem, so the compiler will complain if we don't return a HomeItem. So the only alternative is to return a null HomeItem, so the calling function knows that we didn't find what we were looking for.

However, this possibility means that we need an IsNull member function in the HomeItem class so that the calling program can tell whether it has received a null HomeItem. To see this and the other (relatively minor) changes to the HomeItem interface, let's take a look at the new version of that interface, which is shown in Figure 12.25.

Figure 12.25. The new version of the HomeItem interface (codehmit6.h)
// hmit6.h

class HomeItem
{
friend std::ostream& operator << (std::ostream& os,
 const HomeItem& Item);

friend std::istream& operator >> (std::istream& is, HomeItem& Item);

public:
 HomeItem();
 HomeItem(const HomeItem& Item);
 HomeItem& operator = (const HomeItem& Item);
virtual ~HomeItem();

// Basic: Art objects, furniture, jewelry, etc.
 HomeItem(const xstring& Name, double PurchasePrice,
 long PurchaseDate, const xstring& Description,
 const xstring& Category);

// Music: CDs, LPs, cassettes, etc.
 HomeItem(const xstring& Name, double PurchasePrice,
 long PurchaseDate, const xstring& Description,
 const xstring& Category, const xstring& Artist,
 const Vec<xstring>& Track);

virtual void Write(std::ostream& os);
virtual short FormattedDisplay(std::ostream& os);
virtual xstring GetName();
virtual xstring GetDescription();
virtual bool IsNull();
static HomeItem NewItem();

virtual void Read(std::istream& is);
virtual void Edit();

protected:
 HomeItem(int);
virtual HomeItem* CopyData();

protected:
 HomeItem* m_Worker;
 short m_Count;
};

Susan had a couple of questions about this new version of the interface.

Susan: Why didn't you put the destructor at the end of the interface? After all, it is the last function to be executed.

Steve: I always put the “concrete data type” functions together at the beginning of the public section of the interface. That makes them easier to find.

Susan: Why aren't the constructors virtual if the destructor is?

Steve: Constructors can't be virtual, because the whole point of a virtual function is to allow the program to use the actual type of an object to determine which function is called. When we call a constructor to create an object, the object doesn't exist yet, so there would be no way to determine which virtual function should be called.

As with the HomeInventory class, I've changed all the xstring arguments to const xstring& to improve efficiency by preventing excessive copying. I've done the same with the Track argument to the HomeItemMusic normal constructor; it's now a const Vec<xstring>& rather than a Vec<xstring>, as in the previous version of the header. I've also added two new functions: GetDescription and IsNull. As usual, the HomeItem versions of these functions merely call the corresponding virtual function in the worker object and pass the results back to the calling function. As for the HomeItemBasic version of GetDescription, this function just returns the current value of the m_Description field in its object, so we don't have to bother analyzing it. IsNull is pretty simple too, but we should still take a look at its implementation, shown in Figure 12.26.

Figure 12.26. HomeItemBasic::IsNull (from codehmit6.cpp)
bool HomeItemBasic::IsNull()
{
 if (m_Name == "")
   return true;

 return false;
}

The idea here is that every actual HomeItem has to have a name, so any object that doesn't have one must be a null HomeItem. Therefore, we check whether the name is null. If so, we have a null item, so we return true; otherwise, it's a real item, so we return false to indicate that it's not null.

Susan had a question about the implementation of this function:

Susan: Why isn't there an else in that function? It seems like there should be one.

Steve: Yes, that is a little bit tricky. Let's think about what happens when we execute that function. If the name is equal to "" (nothing), then we will return the value true, so we will never get to the statement that says to return the value false. On the other hand, if the name isn't equal to "", we won't execute the first return statement that returns the value true, but will execute the return statement that returns the value false. So this function will do what we want in either case.

Of course, we don't have to reimplement IsNull in HomeItemMusic because the test for a null music item is identical to the test for a null basic item. Therefore, we can use the HomeItemBasic version of this function should we need to check whether a HomeItemMusic object is “real” or null.

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

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