Movatterモバイル変換


[0]ホーム

URL:




Chapter 12: Abstract Containers

C++ offers several predefined datatypes, all part of theStandard Template Library,which can be used to implement solutions to frequently occurring problems. Thedatatypes discussed in this chapter are allcontainers: you can put stuff inside them, and you canretrieve the stored information from them.

The interesting part is that the kind of data that can be stored inside thesecontainers has been left unspecified at the time the containers wereconstructed. That's why they are spoken of asabstract containers.

Abstract containers rely heavily ontemplates, covered in chapter21 and beyond. To use abstract containers, only a minimal grasp ofthe template concept is required. InC++ a template is in fact a recipefor constructing a function or a complete class. The recipe tries to abstractthe functionality of the class or function as much as possible from the dataon which the class or function operates. As the data types on which thetemplates operate were not known when the template was implemented, thedatatypes are either inferred from the context in which a function template isused, or they are mentioned explicitly when a class template is used (the termthat's used here isinstantiated). In situations where the types areexplicitly mentioned, theangle bracket notation is used to indicatewhich data types are required. For example, below (in section12.2) we'llencounter thepair container, which requires theexplicit mentioning of two data types. Here is apair objectcontaining both anint and astring:

    pair<int, string> myPair;

The objectmyPair is defined as an object holding both anint andastring.

The angle bracket notation is used intensively in the upcomingdiscussion of abstract containers. Actually, understanding this part oftemplates is the only real requirement for using abstract containers. Nowthat we've introduced this notation, we can postpone the more thoroughdiscussion of templates to chapter21, and concentrate ontheir use in this chapter.

Most abstract containers aresequential containers: they containdata that can be stored and retrieved in some sequentialway. Examples are thearray, implementing a fixed-sized array; avector, implementing an extendable array; thelist, implementing a data structure that allows for the easy insertion ordeletion of data; thequeue, also called aFIFO (first in, first out) structure, in which the first element that isentered is the first element to be retrieved again; and thestack,which is afirst in, last out (FILO orLIFO) structure.

In addition to sequential containers several special containers areavailable. Thepair is a basic container in which a pair of values (oftypes that are left open for further specification) can be stored, like twostrings, two ints, a string and a double, etc.. Pairs are often used to returndata elements that naturally come in pairs. For example, themap is anabstract container storing keys and their associated values. Elementsof these maps are returned aspairs.

A variant of thepair is thecomplex container, implementingoperations that are defined on complex numbers.

Atuple (cf. section22.6) generalizes thepair container to adata structure accommodating any number of different data types.

All abstract containers described in this chapter as well as thestringand stream datatypes (cf. chapters5 and6) are part ofthe Standard Template Library.

All but the unordered containers support the following basic set of operators:

Note that before auser-defined type (usually aclass-type) can be stored in a container, theuser-defined type should at least support: Sequential containers can also be initialized usinginitializer lists.

Most containers (exceptions are thestack (section12.3.11),priority_queue (section12.3.5), andqueue (section12.3.4) containers) support members to determine their maximum sizes(through their member functionmax_size).

Virtually all containers support copy construction. If the containersupports copy construction and the container's data type supports moveconstruction, then move construction is automatically used for the container'sdata elements when a container is initialized with an anonymous temporarycontainer.

Closely linked to the standard template library are thegeneric algorithms. These algorithms may be used to performfrequently occurring tasks or more complex tasks than is possible with thecontainers themselves, like counting, filling, merging, filtering etc.. Anoverview of generic algorithms and their applications is given in chapter19. Generic algorithms usually rely on the availability ofiterators, representing begin andend-points for processing data stored inside containers. The abstractcontainers usually support constructors and members expecting iterators, andthey often have members returning iterators (comparable to thestring::begin andstring::end members). In this chapter theiterator concept is not further investigated. Refer to chapter18 forthis.

Containers often collect data during their lifetimes. When a containergoes out of scope, its destructor tries to destroy its data elements. Thisonly succeeds if the data elements themselves are stored inside thecontainer. If the data elements of containersare pointers to dynamically allocated memory then the memory pointed to bythese pointers is not destroyed, resulting in amemory leak. A consequenceof this scheme is that the data stored in a container should often beconsidered the `property' of the container: the container should be able todestroy its data elements when the container's destructor is called. So,normally containers should not contain pointers to data. Also, a containershould not be required to containconst data, asconst dataprevent the use of many of the container's members, like the assignmentoperator.

12.1: Notations used in this chapter

In this chapter about containers, the following notational conventions areused: Some containers, e.g., themap container, contain pairs ofvalues, usually called `keys' and `values'. For such containers the followingnotational convention is used in addition:

12.2: The `pair' container

Thepair container is a rather basic container. It isused to store two elements, calledfirst andsecond, and that's aboutit. Before usingpair containers the header file<utility> must beincluded.

Thepair's data types are specified when thepair object isdefined (or declared) using the template's angle bracket notation (cf. chapter21). Examples:

    pair<string, string> piper("PA28", "PH-ANI");    pair<string, string> cessna("C172", "PH-ANG");

here, the variablespiper andcessna are defined aspairvariables containing twostrings. Both strings can be retrieved using thefirst andsecond fields of thepair type:

    cout << piper.first << '\n' <<      // shows 'PA28'            cessna.second << '\n';      // shows 'PH-ANG'

Thefirst andsecond members can also be used to reassign values:

    cessna.first = "C152";    cessna.second = "PH-ANW";

If apair object must be completely reassigned, ananonymous pair object can be used as theright-hand operand ofthe assignment. An anonymous variable defines a temporary variable (whichreceives no name) solely for the purpose of (re)assigning another variable ofthe same type. Its generic form is

    type(initializer list)

Note that when apair object is used the type specification is notcompleted by just mentioning the containernamepair. It also requires thespecification of the data types which are stored within the pair. For this the(template)angle bracket notation is used again. E.g., the reassignmentof thecessna pair variable could have been accomplished as follows:

    cessna = pair<string, string>("C152", "PH-ANW");

In cases like these, thetype specification can become quiteelaborate, in which caseusing declarations can be used to improvereadability. If manypair<type1, type2> clauses areused in a source, the typing effort may be reduced and readability might beimproved by first defining a name for the clause, and then using the definedname later. E.g.,

    using pairStrStr = pair<string, string>;    cessna = pairStrStr("C152", "PH-ANW");

All abstract containers are class templates, and thetypes for which class templates are initialized are commonly specified betweenpointed brackets following the class template's name. However, the compilermay be able to deduce the container's types fromthe types of arguments that are specified when constructing thecontainer. E.g., when defining

    pair values{ 1, 1.5 };

the compiler deduces thatvalues.first is anint andvalues.second is adouble. Sometimes the class template's types cannotbe deduced. In those cases the intended types must explicitly be specified:

    pair<int, double> values;

Although the compiler will deduce types whenever it can, it might notdeduce the types we had in mind. Had we defined

    pair cessna{ "C172", "PH-BVL" };

then the compilation would succeed, but an expression likecout << cessna.first.length() would not compile, as"C172" isa NTBS, and hencecessna.first is achar *. In this case simplyappending ans to the NTBSs fixes the problem, but such a simple fix mightnot always be available. Section12.3.2 has contains more informationabout deducing template parameter types.

Apart from this (and the basic set of operations (assignment andcomparisons)) thepair offers no furtherfunctionality. It is, however,a basic ingredient of the upcoming abstract containersmap, multimap andhash_map.

C++ also offers ageneralized pair container: thetuple, coveredin section22.6.

12.3: Available Containers

12.3.1: The `array' container

Thearray class implements afixed-size array. Before using thearraycontainer the<array> header file must be included.

To define astd::array both the data type of its elements and its sizemust be specified: the data type is given after an opening angle bracket,immediately following the `array' container name. The array's size isprovided after the data type specification. Finally, a closing angle bracketcompletes the array's type. Specifications like this are common practice withcontainers. The combination ofarray, type and size defines atype. As a result,array<string, 4> defines another type thanarray<string, 5>, and a function explicitly defining anarray<Type, N>parameter will not accept anarray<Type, M> argument ifN andMare unequal.

The array's size may may be defined as 0 (although such an array probably haslittle use as it cannot store any element). The elements of an array arestored contiguously. Ifarray<Type, N> arr has been defined, then&arr[n] + m == &arr[n + m], assuming than0 <= n < N and0 <= n + m< N.

The following constructors, operators, and member functions are available:

Using anarray rather than a standardC style array offers severaladvantages: In general, when looking for a sequential data structure, thearray orvector (introduced in the next section) should be your `weapon ofchoice'. Only if these containers demonstrably do not fit the problem at handyou should use another type of container.

12.3.2: The `vector' container

Thevector class implements anexpandable array. Before using thevectorcontainer the<vector> header file must be included.

The following constructors, operators, and member functions are available:

12.3.3: The `list' container

Thelist container implements a list data structure.Before using alist container the header file<list> must be included.

The organization of alist is shown in figure10.

Figure 10: A list data-structure

Figure10 shows that a list consists of separatelist-elements, connected by pointers. The list can be traversed in two directions: starting atFront thelist may be traversed from left to right, until the 0-pointer is reached atthe end of the rightmost list-element. The list can also be traversed fromright to left: starting atBack, the list is traversed from right to left,until eventually the 0-pointer emanating from the leftmost list-element isreached.

As a subtlety note that the representation given in figure10 is notnecessarily used in actual implementations of the list. For example, considerthe following little program:

    int main()    {        list<int> l;        cout << "size: " << l.size() << ", first element: " <<                l.front() << '\n';    }

When this program is run it might actually produce the output:

    size: 0, first element: 0

Its front element can even be assigned a value. In this case theimplementor has chosen to provide the list with a hidden element. The listactually is acircular list, where the hidden element serves as terminatingelement, replacing the 0-pointers in figure10. As noted, this is asubtlety, which doesn't affect the conceptual notion of a list as a datastructure ending in 0-pointers. Note also that it is well known that variousimplementations of list-structures are possible (cf.Aho, A.V.,Hopcroft J.E. andUllman, J.D., (1983)Data Structures and Algorithms (Addison-Wesley)).

Both lists and vectors are often appropriate data structures in situationswhere an unknown number of data elements must bestored. However, there are some rules of thumb to followwhen selecting the appropriate data structure.

At present lists aren't as useful anymore as they used to be (whencomputers were much slower and more memory-constrained). Except maybe forsome rare cases, avector should be the preferred container; even whenimplementing algorithms traditionally using lists.

Other considerations related to the choice between lists and vectorsshould also be given some thought. Although it is true that the vector is ableto grow dynamically, thedynamic growth requires data-copying.Clearly, copying a million large data structures takes a considerable amountof time, even on fast computers. On the other hand, inserting a large numberof elements in a list doesn't require us tocopy non-involved data. Inserting a new element in a list merelyrequires us to juggle some pointers. In figure11 this is shown: anew element is inserted between the second and third element, creating a newlist of four elements.

Figure 11: Adding a new element to a list

Removing an element from a list is also fairly easy. Starting againfrom the situation shown in figure10, figure12 showswhat happens if element two is removed from our list. Again: only pointersneed to be juggled. In this case it's even simpler than adding an element:only two pointers need to be rerouted.

Figure 12: Removing an element from a list

To summarize the comparison between lists and vectors: it's probably bestto conclude that there is no clear-cut answer to the question what datastructure to prefer. There are rules of thumb, which may be adhered to. But ifworse comes to worst, aprofiler may be required to find out what's best.

Thelist container offers the following constructors, operators, andmember functions:

12.3.4: The `queue' container

Thequeue class implements aqueue data structure. Before using aqueue container the header file<queue> must be included.

A queue is depicted in figure13.

Figure 13: A queue data-structure

In figure13 it is shown that a queue has one point (theback) where items can be added to the queue, and one point (thefront)where items can be removed (read) from the queue. Aqueue is thereforealso called aFIFO data structure, forfirst in, first out. It ismost often used in situations where events should be handled in the same orderas they are generated.

The following constructors, operators, and member functions are availablefor thequeue container:

Note that the queue does not support iterators or an index operator. Theonly elements that can be accessed are its front and back element. A queuecan be emptied by:

12.3.5: The `priority_queue' container

Thepriority_queue class implements apriority queue data structure.Before using apriority_queue container the<queue> header file musthave been included.

A priority queue is identical to aqueue, but allows the entry of dataelements according topriority rules. A real-life priority queue isfound, e.g., at airport check-in terminals. At a terminal the passengersnormally stand in line to wait for their turn to check in, but late passengersare usually allowed to jump the queue: they receive a higher priority thanother passengers.

The priority queue usesoperator< of the data type stored in thepriority queue to decide about the priority of the data elements. Thesmaller the value, thelower the priority. So, the priority queuecould be used to sort values while they arrive. A simple example of sucha priority queue application is the following program: it reads words fromcin and writes a sorted list of words tocout:

#include <iostream>#include <string>#include <queue>using namespace std;int main(){    priority_queue<string> q;    string word;    while (cin >> word)        q.push(word);    while (q.size())    {        cout << q.top() << '\n';        q.pop();    }}

Unfortunately, the words are listed in reversed order: because of theunderlying<-operator the words appearing later in theASCII-sequenceappear first in the priority queue. A solution to that problem is to define awrapper class around thestring datatype, reversingstring'soperator<. Here is the modified program:

#include <iostream>#include <string>#include <queue>class Text{    std::string d_s;    public:        Text(std::string const &str)        :            d_s(str)        {}        operator std::string const &() const        {            return d_s;        }        bool operator<(Text const &right) const        {            return d_s > right.d_s;        }};using namespace std;int main(){    priority_queue<Text> q;    string word;    while (cin >> word)        q.push(word);    while (q.size())    {        word = q.top();        cout << word << '\n';        q.pop();    }}

Other possibilities to achieve the same exist. One would be to store thecontent of the priority queue in, e.g., a vector, from which the elements canbe read in reversed order.

The following constructors, operators, and member functions are availablefor thepriority_queue container:

Note that the priority queue does not support iterators or an indexoperator. The only element that can be accessed is its top element. Apriority queue can be emptied by:

12.3.6: The `deque' container

Thedeque (pronounce: `deck') class implements adoubly ended queue data structure (deque). Before using adequecontainer the header file<deque> must be included.

Adeque is comparable to a queue, but it allows for reading andwriting at both ends. Actually, thedeque data type supports a lot morefunctionality than thequeue, as illustrated by the following overview ofavailable member functions. Adeque is a combination of avector andtwo queues, operating at both ends of the vector. In situations where randominsertions and the addition and/or removal of elements at one or both sides ofthe vector occurs frequently using adeque should be considered.

The following constructors, operators, and member functions are available fordeques:

12.3.7: The `map' container

Themap class offers a (sorted)associative array. Before using amap container the<map> header file must be included.

Amap is filled withkey, value pairs, which may be of anycontainer-accepted type. Since types are associated with both the key and thevalue, we must specifytwo types in the angle bracket notation, comparableto the specification we've seen with thepair container (cf. section12.2). The first type represents the key's type, the second typerepresents the value's type. Thekey is used to access its associatedinformation. Themap sorts the keys using theiroperator<, where thesmallest key values appears as first element in the map. For example, amap in which the key is astring and the value is adouble can bedefined as follows:

    map<string, double> object;
That information is called thevalue. For example, a phone book uses thenames of people as the key, and uses the telephone number and maybe otherinformation (e.g., the zip-code, the address, the profession) as value. Sinceamap sorts its keys, thekey'soperator< must be defined, and itmust be sensible to use it. For example, it is generally a bad idea to usepointers for keys, as sorting pointers is something different than sorting thevalues pointed at by those pointers. In addition to thekey, value types,a third type defines the comparison class, used to compare two keys. Bydefault, the comparison class isstd::less<KeyType> (cf. section18.1.2), using the key type'soperator< to compare two keyvalues. For key typeKeyType and value typeValueType the map's typedefinition, therefore, looks like this:
    map<KeyType, ValueType, std::less<KeyType>>

The two fundamental operations on maps are the storage ofkey, valuecombinations, and the retrieval of values, given their keys. The indexoperator using a key as the index, can be used for both. If the index operatoris used aslvalue, the expression'srvalue is inserted into themap. If it is used asrvalue, the key's associated value is retrieved.Each key can be stored only once in amap. If the same key is enteredagain, the new value replaces the formerly stored value, which is lost.

A specifickey, value combination can implicitly or explicitly be insertedinto amap. If explicit insertion is required, thekey, valuecombination must be constructed first. For this, everymap defines avalue_type which may be used to create values that can be stored in themap. For example, a value for amap<string, int> can be constructed asfollows:

    map<string, int>::value_type siValue{ "Hello", 1 };

Thevalue_type is associated with themap<string, int>: thetype of the key isstring, the type of the value isint. Anonymousvalue_type objects are also often used. E.g.,

    map<string, int>::value_type{ "Hello", 1 };

Instead of using the linemap<string, int>::value_type(...) over andover again, a using declaration is frequently used to reduce typing and toimprove readability:

    using StringIntValue = map<string, int>::value_type;

Now values for themap<string, int> may be specified this way:

    StringIntValue{ "Hello", 1 };

Alternatively,pairs may be used to representkey, valuecombinations used by maps:

    pair<string, int>{ "Hello", 1 };

12.3.7.1: The `map' constructors

The following constructors are available for themap container:

12.3.7.2: The `map' operators

The map supports, in addition to the standard operators for containers,theindex operator.

The index operator may be used to retrieve or reassign individual elements ofthe map. The argument of the index operator is called akey.

If the provided key is not available in themap, a new data element isautomatically added to themap using the default value or defaultconstructor to initialize the value part of the new element. This defaultvalue is returned if the index operator is used as an rvalue.

When initializing a new or reassigning another element of the map, the type ofthe right-hand side of the assignment operator must be equal to (or promotableto) the type of the map's value part. E.g., to add or change the value ofelement"two" in a map, the following statement can be used:

    mapsm["two"] = MyClass{};

12.3.7.3: The `map' public members

The following member functions are available for themap container:

12.3.7.4: The `map': a simple example

As mentioned at the beginning of section12.3.7, themap representsa sorted associative array. In amap the keys are sorted. If anapplication must visit all elements in a map thebegin andenditerators must be used.

The following example illustrates how to make a simple table listing all keysand values found in a map:

    #include <iostream>    #include <iomanip>    #include <map>    using namespace std;    int main()    {        pair<string, int>            pa[] =            {                pair<string,int>("one", 10),                pair<string,int>("two", 20),                pair<string,int>("three", 30),            };        map<string, int>            object(&pa[0], &pa[3]);        for        (            map<string, int>::iterator it = object.begin();                it != object.end();                    ++it        )            cout << setw(5) << it->first.c_str() <<                    setw(5) << it->second << '\n';    }    /*        Generated output:      one   10    three   30      two   20    */

12.3.8: The `multimap' container

Like themap, themultimap class implements a (sorted)associative array. Before using amultimap container the headerfile<map> must be included.

The main difference between themap and themultimap is that themultimap supports multiple values associated with the same key, whereas themap contains single-valued keys. Note that the multimap also accepts multipleidentical values associated with identical keys.

Themap and themultimap have the same set of constructors and memberfunctions, with the exception of theindex operator which is not supported with the multimap. This is understandable: ifmultiple entries of the same key are allowed, which of the possible valuesshould be returned forobject[key]?

Refer to section12.3.7 for an overview ofthemultimap member functions. Some member functions, however, deserveadditional attention when used in the context of themultimapcontainer. These members are discussed below.

Although the functionslower_bound andupper_bound actidentically in themap andmultimap containers, their operation in amultimap deserves some additional attention. The next example illustrateslower_bound,upper_bound andequal_range applied to amultimap:
    #include <iostream>    #include <map>    using namespace std;    int main()    {        pair<string, int> pa[] =        {            pair<string,int>("alpha", 1),            pair<string,int>("bravo", 2),            pair<string,int>("charlie", 3),            pair<string,int>("bravo", 6),   // unordered `bravo' values            pair<string,int>("delta", 5),            pair<string,int>("bravo", 4),        };        multimap<string, int> object(&pa[0], &pa[6]);        using msiIterator = multimap<string, int>::iterator;        msiIterator it = object.lower_bound("brava");        cout << "Lower bound for `brava': " <<                it->first << ", " << it->second << '\n';        it = object.upper_bound("bravu");        cout << "Upper bound for `bravu': " <<                it->first << ", " << it->second << '\n';        pair<msiIterator, msiIterator>            itPair = object.equal_range("bravo");        cout << "Equal range for `bravo':\n";        for (it = itPair.first; it != itPair.second; ++it)            cout << it->first << ", " << it->second << '\n';        cout << "Upper bound: " << it->first << ", " << it->second << '\n';        cout << "Equal range for `brav':\n";        itPair = object.equal_range("brav");        for (it = itPair.first; it != itPair.second; ++it)            cout << it->first << ", " << it->second << '\n';        cout << "Upper bound: " << it->first << ", " << it->second << '\n';    }    /*        Generated output:        Lower bound for `brava': bravo, 2        Upper bound for `bravu': charlie, 3        Equal range for `bravo':        bravo, 2        bravo, 6        bravo, 4        Upper bound: charlie, 3        Equal range for `brav':        Upper bound: bravo, 2    */
In particular note the following characteristics:

12.3.9: The `set' container

Theset class implements asorted collection of values. Before usingset containers the<set> header file must be included.

A set contains unique values (of a container-acceptable type). Each valueis stored only once, and theset sorts its values using theiroperator<, where the smallest values appears as first element in the set.

A specific value can be explicitly created: Everyset defines avalue_type which may be used to create values that can be stored in theset. For example, a value for aset<string> can be constructed asfollows:

    set<string>::value_type setValue{ "Hello" };

Like thestd::map container, thestd::set also has an additionalparameter declaring the class that is used for comparing values in the set.For value typeValueType the set's type definition, therefore, looks likethis:

    set<ValueType, std::less<ValueType>>

Thevalue_type is associated with theset<string>. Anonymousvalue_type objects are also often used. E.g.,

    set<string>::value_type{ "Hello" };

Instead of using the lineset<string>::value_type(...) overand over again, a using declaration is often used to reduce typing and toimprove readability:

    using StringSetValue = set<string>::value_type;

Now values for theset<string> may be constructed as follows:

    StringSetValue{ "Hello" };

Alternatively, values of the set's type may be usedimmediately. In that case the value of typeType is implicitlyconverted to aset<Type>::value_type.

The following constructors, operators, and member functions are availablefor theset container:

12.3.10: The `multiset' container

Like theset, themultiset class implements a sorted collection of values. Beforeusingmultiset containers the header file<set> must be included.

The main difference between theset and themultiset is that themultiset supports multiple entries of the same value, whereas thesetcontains unique values.

Theset and themultiset have the same set of constructors and memberfunctions. Refer to section12.3.9 for an overview of the member functionsthat can be used with themultiset. Some member functions, however,behave slightly different than their counterparts of thesetcontainer. Those members are:

Although the functionslower_bound andupper_bound act identicallyin theset andmultiset containers, their operation in amultisetdeserves some additional attention. With amultiset containerlower_bound andupper_bound produce the same result for non-existingkeys: they both return the first element having a key exceeding the providedkey.

Here is an example showing the use of various member functions of amultiset:

    #include <iostream>    #include <set>    using namespace std;    int main()    {        string            sa[] =            {                "alpha",                "echo",                "hotel",                "mike",                "romeo"            };        multiset<string>            object(&sa[0], &sa[5]);        object.insert("echo");        object.insert("echo");        multiset<string>::iterator            it = object.find("echo");        for (; it != object.end(); ++it)            cout << *it << " ";        cout << '\n';        cout << "Multiset::equal_range(\"ech\")\n";        pair        <            multiset<string>::iterator,            multiset<string>::iterator        >            itpair = object.equal_range("ech");        if (itpair.first != object.end())            cout << "lower_bound() points at " << *itpair.first << '\n';        for (; itpair.first != itpair.second; ++itpair.first)            cout << *itpair.first << " ";        cout << '\n' <<                object.count("ech") << " occurrences of 'ech'" << '\n';        cout << "Multiset::equal_range(\"echo\")\n";        itpair = object.equal_range("echo");        for (; itpair.first != itpair.second; ++itpair.first)            cout << *itpair.first << " ";        cout << '\n' <<                object.count("echo") << " occurrences of 'echo'" << '\n';        cout << "Multiset::equal_range(\"echoo\")\n";        itpair = object.equal_range("echoo");        for (; itpair.first != itpair.second; ++itpair.first)            cout << *itpair.first << " ";        cout << '\n' <<                object.count("echoo") << " occurrences of 'echoo'" << '\n';    }    /*        Generated output:        echo echo echo hotel mike romeo        Multiset::equal_range("ech")        lower_bound() points at echo        0 occurrences of 'ech'        Multiset::equal_range("echo")        echo echo echo        3 occurrences of 'echo'        Multiset::equal_range("echoo")        0 occurrences of 'echoo'    */

12.3.11: The `stack' container

Thestack class implements astack data structure. Before usingstack containers the header file<stack> must be included.

A stack is also called afirst in, last out (FILO orLIFO) datastructure as the first item to enter the stack is the last item to leave. Astack is an extremely useful data structure in situations where data musttemporarily remain available. For example, programs maintain a stack to storelocal variables of functions: the lifetime of these variables is determined bythe time these functions are active, contrary to global (or static local)variables, which live for as long as the program itself lives. Another exampleis found in calculators using theReverse Polish Notation (RPN), in which the operands of operators arekept in a stack, whereas operators pop their operands off the stack andpush the results of their work back onto the stack.

As an example of the use of a stack, consider figure14, in whichthe content of the stack is shown while the expression(3 + 4) * 2 isevaluated. In the RPN this expression becomes3 4 + 2 *, and figure14 shows the stack content after eachtoken (i.e., theoperands and the operators) is read from the input. Notice that each operandis indeed pushed on the stack, while each operator changes the content of thestack.

Figure 14: The content of a stack while evaluating3 4 + 2 *

The expression is evaluated in five steps. The caret between the tokensin the expressions shown on the first line of figure14 shows whattoken has just been read. The next line shows the actual stack-content, andthe final line shows the steps for referential purposes. Note that at step 2,two numbers have been pushed on the stack. The first number (3) is now atthe bottom of the stack. Next, in step 3, the+ operator is read. Theoperator pops two operands (so that the stack is empty at that moment),calculates their sum, and pushes the resulting value (7) on thestack. Then, in step 4, the number2 is read, which is dutifully pushed onthe stack again. Finally, in step 5 the final operator* is read, whichpops the values2 and7 from the stack, computes their product, andpushes the result back on the stack. This result (14) could then be poppedto be displayed on some medium.

From figure14 we see that a stack has one location (thetop)where items can be pushed onto and popped off the stack. This top element isthe stack's only immediately visible element. It may be accessed and modifieddirectly.

Bearing this model of the stack in mind, let's see what we formally can dowith thestack container. For thestack, the following constructors,operators, and member functions are available:

The stack does not support iterators or an indexoperator. The only elements that can be accessed is its top element. To empty a stack:

12.3.12: The `unordered_map' container (`hash table')

InC++ hash tables are available as objects of the classunordered_map.

Before usingunordered_map orunordered_multimap containers the headerfile<unordered_map> must be included.

Theunordered_map class implements anassociative array in which theelements are stored according to somehashing scheme. As discussed, themap is a sorted data structure. The keys in maps are sorted using theoperator< of the key's data type. Generally, this is not the fastest wayto either store or retrieve data. The main benefit of sorting is that alisting of sorted keys appeals more to humans than an unsorted list. However,a by far faster way to store and retrieve data is to usehashing.

Hashing uses a function (called thehash function) to compute an(unsigned) number from the key, which number is thereupon used as an index inthe table storing the keys and their values. This number is called thebucket number. Retrieval of a key is as simple as computing thehash value of the provided key, and looking in the table at the computedindex location: if the key is present, it is stored in the table, at thecomputed bucket location and its value can be returned. If it's not present,the key is not currently stored in the container.

Collisions occur when a computed index position is alreadyoccupied by another element. For these situations the abstract containers havesolutions available. A simple solution, used byunordered_maps, consistsof usinglinear chaining, which uses linked list to store colliding tableelements.

The termunordered_map is used rather thanhash to avoid namecollisions with hash tables developed before they were added to the language.

Because of the hashing method, theefficiency of aunordered_map interms of speed should greatly exceed the efficiency of themap. Comparableconclusions may be drawn for theunordered_set, theunordered_multimapand theunordered_multiset.

12.3.12.1: The `unordered_map' constructors

When defining anunordered_map type five template arguments must bespecified :

The generic definition of anunordered_map container looks like this:

    std::unordered_map <KeyType, ValueType, hash type, predicate type,                        allocator type>

WhenKeyType isstd::string or a built-in type then default typesare available for the hash type and the predicate type. In practice theallocator type is not specified, as the default allocator suffices. In thesecases anunordered_map object can be defined by merely specifying the key-and value types, like this:

    std::unordered_map<std::string, ValueType> hash(size_t size = implSize);

Here,implSize is the container's default initial size, which isspecified by the implementor. The map's size is automatically enlarged by theunordered_map when necessary, in which case the containerrehashes allits elements. In practice the defaultsize argument provided by theimplementor is completely satisfactory.

TheKeyType frequently consists of text. So, aunordered_map using astd::string KeyType is frequently used. Be careful not to use a plainchar const * key_type as twochar const * values pointing to equalC-strings stored at different locations are considered to be differentkeys, as their pointer values rather than their textual content arecompared. Here is an example showing how achar const * KeyType can beused. Note that in the example no arguments are specified when constructingmonths, since default values and constructors are available:

    #include <unordered_map>    #include <iostream>    #include <string>    #include <cstring>    using namespace std;    struct EqualCp    {        bool operator()(char const *l, char const *r) const        {            return strcmp(l, r) == 0;        }    };    struct HashCp    {        size_t operator()(char const *str) const        {            return std::hash<std::string>()(str);        }    };    int main()    {        unordered_map<char const *, int, HashCp, EqualCp> months;        // or explicitly:            unordered_map<char const *, int, HashCp, EqualCp>                                      monthsTwo(61, HashCp(), EqualCp());        months["april"] = 30;        months["november"] = 31;        string apr("april");    // different pointers, same string        cout << "april     -> " << months["april"] << '\n' <<                "april     -> " << months[apr.c_str()] << '\n';    }

If otherKeyTypes must be used, then theunordered_map's constructorrequires (constant references to) a hash function object, computing a hashvalue from a key value, and a predicate function object, returningtrue iftwounordered_map::key_type objects are identical. Agenericalgorithm (see chapter19) exists performing tests of equality(i.e.,equal_to). These tests can be used if the key's data type supportsthe equality operator. Alternatively, an overloadedoperator== orspecializedfunction object could be constructed returningtrue if twokeys are equal andfalse otherwise.

Constructors

Theunordered_map supports the following constructors:

The following example shows a program using an unordered_map containingthe names of the months of the year and the number of days these months(usually) have. Then, using the subscript operator the days in several monthsare displayed (the predicate used here is the generic algorithmequal_to<string>, which is provided by the compiler as the default fourthargument of theunordered_map constructor):

    #include <unordered_map>    #include <iostream>    #include <string>    using namespace std;    int main()    {        unordered_map<string, int> months;        months["january"] = 31;        months["february"] = 28;        months["march"] = 31;        months["april"] = 30;        months["may"] = 31;        months["june"] = 30;        months["july"] = 31;        months["august"] = 31;        months["september"] = 30;        months["october"] = 31;        months["november"] = 30;        months["december"] = 31;        cout << "september -> " << months["september"] << '\n' <<                "april     -> " << months["april"] << '\n' <<                "june      -> " << months["june"] << '\n' <<                "november  -> " << months["november"] << '\n';    }    /*        Generated output:    september -> 30    april     -> 30    june      -> 30    november  -> 30    */

12.3.12.2: The `unordered_map' public members

Theunordered_map supports the index operator operating identically to themap's index operator: a (const) reference to theValueType associatedwith the providedKeyType's value is returned. If not yet available, thekey is added to theunordered_map, and a defaultValueType value isreturned. In addition, it supportsoperator==.

Theunordered_map provides the following member functions (key_type,value_type etc. refer to the types defined by theunordered_map):

12.3.12.3: The `unordered_multimap' container

Theunordered_multimap allows multiple objects using the same keys to bestored in an unordered map. Theunordered_multimap container offers thesame set of members and constructors as theunordered_map, but without theunique-key restriction imposed upon theunordered_map.

Theunordered_multimap does not offeroperator[] and does not offerat members.

Below all members are described whose behavior differs from the behavior ofthe correspondingunordered_map members:

12.3.13: The `unordered_set' container

Theset container, like themap container, orders its elements. Ifordering is not an issue, but fast lookups are, then a hash-based set and/ormulti-set may be preferred.C++ provides such hash-based sets andmulti-sets: theunordered_set andunordered_multiset.

Before using these hash-based set containers the header file<unordered_set> must be included.

Elements stored in theunordered_set are immutable, but they can beinserted and removed from the container. Different from theunordered_map,theunordered_set does not use aValueType. The set merely storeselements, and the stored element itself is its own key.

Theunordered_set has the same constructors as theunordered_map, butthe set'svalue_type is equal to itskey_type.

When defining anunordered_set type four template arguments must bespecified :

The generic definition of anunordered_set container looks like this:

    std::unordered_set <KeyType, hash type, predicate type, allocator type>

WhenKeyType isstd::string or a built-in type then default typesare available for the hash type and the predicate type. In practice theallocator type is not specified, as the default allocator suffices. In thesecases anunordered_set object can be defined by merely specifying the key-and value types, like this:

    std::unordered_set<std::string> rawSet(size_t size = implSize);

Here,implSize is the container's default initial size, which isspecified by the implementor. The set's size is automatically enlarged whennecessary, in which case the containerrehashes all its elements. Inpractice the defaultsize argument provided by the implementor iscompletely satisfactory.

Theunordered_set supports the following constructors:

Theunordered_set does not offer an index operator, and it does not offeranat member. Other than those, it offers the same members as theunordered_map. Below the members whose behavior differs from the behaviorof theunordered_map are discussed. For a description of the remainingmembers, please refer to section12.3.12.2.

12.3.13.1: The `unordered_multiset' container

Theunordered_multiset allows multiple objects using the same keys to bestored in an unordered set. Theunordered_multiset container offers thesame set of members and constructors as theunordered_set, but without theunique-key restriction imposed upon theunordered_set.

Below all members are described whose behavior differs from the behavior ofthe correspondingunordered_set members:

12.3.14: Heterogeneous lookup

The associative containers offered byC++ allow us to find a value (orvalues) matching a given key. Traditionally, the type of the key used for thelookup must match the container's key type.

Since the C++14 standard arbitrary lookup key types can be used provided acomparison operator is available to compare that type with the container's keytype. Thus, achar const * key (or any other type for which anoperator< overload forstd::string is available) can be used to lookupvalues in amap<std::string, ValueType>. This is calledheterogeneous lookup.

Heterogeneous lookup is allowed when the comparator given to the associativecontainer does allow this. The standard library classesstd::less andstd::greater were augmented to allow heterogeneous lookup.

12.4: The `complex' container

Thecomplex container defines the standard operations that can beperformed oncomplex numbers. Before usingcomplex containers theheader file<complex> must be included.

The complex number's real and imaginary types are specified as the container'sdata type. Examples:

    complex<double>    complex<int>    complex<float>

Note that the real and imaginary parts of complex numbers have the samedatatypes.

When initializing (or assigning) a complex object, theimaginary partmay be omitted from the initialization or assignment resulting in its valuebeing 0 (zero). By default, both parts are zero.

Below it is silently assumed that the usedcomplex type iscomplex<double>. Given this assumption, complex numbers may be initializedas follows:

Anonymous complex values may also be used. In the next example twoanonymous complex values are pushed on a stack of complex numbers, to bepopped again thereafter:
    #include <iostream>    #include <complex>    #include <stack>    using namespace std;    int main()    {        stack<complex<double>>            cstack;        cstack.push(complex<double>(3.14, 2.71));        cstack.push(complex<double>(-3.14, -2.71));        while (cstack.size())        {            cout << cstack.top().real() << ", " <<                    cstack.top().imag() << "i" << '\n';            cstack.pop();        }    }    /*        Generated output:    -3.14, -2.71i    3.14, 2.71i    */

The following member functions and operators are defined for complexnumbers (below,value may be either a primitivescalar type or acomplex object):




[8]ページ先頭

©2009-2025 Movatter.jp