Chapter 7: Abstract Containers

We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Mail to Frank Brokken or use an e-mail form. Please state the concerned document version, found in the title.



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


The interesting part is that the kind of data that can be stored inside these containers has been left unspecified by the time the containers were constructed. That's why they are spoken of as abstract containers.


The abstract containers rely heavily on templates, which are covered near the end of the C++ Annotations, in chapter 16. However, in order to use the abstract containers, only a minimal grasp of the template concept is needed. In C++ a template is in fact a recipe for constructing a function or a complete class. The recipe tries to abstract the functionality of the class or function as much as possible from the data on which the class or function operate. As the types of the data on which the templates operate were not known by the time the template was constructed, the datatypes are either inferred from the context in which a template function is used, or they are mentioned explicitly by the time a template class is used (the term that's used here is instantiated). In situations where the types are explicitly mentioned, the angular bracket notation is used to indicate which data types are required. For example, below (in section 7.1) we'll encounter the pair container, which requires the explicit mentioning of two data types. E.g., to define a pair variable containing both an int and a string, the notation


            pair<int, string>
                myPair;
 
is used. Here, myPair is defined as a pair variable, containing both an int and a string.


The angular bracket notation is used intensively in the following discussion of the abstract container. Actually, understanding this part of templates is the only real requirement for being able to use the abstract containers. Now that we've introduced this notation, we can postpone the more thorough discussion of templates to chapter 16, and get on with their use in the form of the abstract container classes.


Most of the abstract containers are sequential containers: they represent a series of data which can be stored and retrieved in some sequential way. Examples are the vector, imlementing an extendable array, the list, implementing a datastructure in which insertions and deletions can be easily realized, a queue, in which the first element that is entered will be the first element that will be retrieved, and the stack, which is a first in, last out datastructure.


Apart from the sequential containers, several special containers are available. The pair is a basic container in which a pair of values (of types that are left open for further specification) can be stored, like two strings, two ints, a string and a double, etc..


A variant of the pair is the complex container, which implements operations that are defined on complex numbers.


All abstract containers described in this chapter and the string datatype discussed in section 3.3.3 are part of the standard template library. There exists also an abstract container for the implementation of a hashtable, but that container is not (yet) accepted by the ISO/ANSI standard. The final section of this chapter will cover the hashtable to some extent.


All containers support the = operator to assign two containers of the same type to each other. All containers also support the ==, !=, <, <=, > and >= operators.


If a user-defined type (usually a class-type) is to be stored in a container, the user-defined type must support


Closely linked to the standard template library are the generic algorithms. These algorithms may be used to perform even more tasks than is possible with the containers themselves, like counting, filling, merging, filtering etc.. An overview of the generic algorithms and their applications is given in chapter 10. Generic algorithms usually rely on the availability of iterators, which represent begin and endpoints for processing data stored inside the containers. The abstract containers normally have constructors and members using iterators themselves, and they have members returning iterators (comparable to the string::begin() and string::end() members). In the remainder of this chapter the use of iterators is not really covered. Refer to chapter 10 for the discussion of iterators.


The url http://www.sgi.com/Technology/STL is worth visiting by those readers who want more information about the abstract containers and the standard template library than can be provided in the C+++ annotations.



7.1: The `pair' container

The pair container is a rather basic container. It can be used to store two elements, called first and second, and that's about it. To define a variable as a pair container, the header file

            #include <utility>
 
must be included. Its data types are defined when the pair variable is defined, using the standard template (see chapter Templates) notation:

            pair<string, string>
                piper("PA28", "PH-ANI"),
                cessna("C172", "PH-ANG");
    
here, the variables piper and cessna are defined as pair variables containing two strings. Both strings can be retrieved using the first and second fields of the pair type:

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


The first and second members can also be used to reassign values:


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


If a pair variable must be completely reassigned, it is also possible to use an anonymous pair variable as the right-hand side operand of the assignment. An anonymous variable defines a temporary variable (which receives no name) solely for the purpose of (re)assigning another variable of the same type. Its general form is


            type(initializer list)
    
Note, however, that with a pair variable the type specification is not completed when the containername pair has been mentioned. It also requires the data types which are stored inside the pair. For this the angular bracket notation is used again. E.g., the reassignment of the cessna pair variable could also have been accomplished as follows:

            cessna = pair<string, string>("C152", "PH-ANW");
    
In cases like this, the type specification can become quite elaborate, which has caused a revival of interest in the possibilities offered by the typedef keyword. If a lot of pair<type1, type2> clauses are used in a source, the amount of typing may be reduced and legibility might be improved by first defining a name for the clause, and then using the defined name later on. E.g.,

            typedef pair<string, string> pairStrStr
            ...
            cessna = pairStrStr("C152", "PH-ANW")
    


Apart from this (and the basic set of operations (assignment and comparisons) the pair has no further special features. It is, however, a basic ingredient of the upcoming abstract containers map, multimap and hash_map.


7.2: Sequential Containers


7.2.1: The `vector' container

The vector class implements an (expandable) array. To use the vector, the header file vector must be included:

            #include <vector>
 
Vectors can be used like arrays, and can be defined with a fixed number of elements. E.g., to define a vector of 30 ints we do

            vector<int>
                iVector(30);


Note the realization of the data type that is to be used: the datatype is given between angular brackets after te vector container name. So, a vector of 30 strings is defined as


            vector<string>
                strVector(30);


One of the nice characteristics of defining such a vector is that it's initialized to the data type's default value. If there's a default constructor, it is called in cases the datatype is a class. So, for the int vector we know its values are 0.


Another way to initialize the vector is to use explicit initialization values:


            vector<int>
                iVector(1, 2, 3);


As with string variables,

Note here that the last element mentioned is not used for the initialization. This is a simple example of the use of iterators, in which the range of values that is used starts at the first value, and includes all elements up to, but not including the last value mentioned. The standard notation for this is [begin, end).


Also available are:


Note that a vector may be defined without size: vector<int> ivect;.


This defines an empty vector, without any element at all. So, a statement like ivect[0] = 18; would (in this case) be an error, as there isn't any element as yet. In this case, the preferred idiom is ivect.push_back(18);



7.2.2: The `list' container

The list class implements a list datastructure. To use the list, the header file list must be included:

            #include <list>
 


A list is depicted in figure 5.

figure 5 is shown here.
figure 5: A list data-structure


In figure 5 it is shown that a list consists of separate data-items, connected to each other by pointers. The list can be traversed in two ways: starting at the Front the list may be traversed from left to right, until the 0-pointer is reached at the end of the rightmost data-item. The list can also be traversed from right to left: starting at the Back, the list is traversed from right to left, until eventually the 0-pointer emanating from the leftmost data-item is reached.


Lists and vectors are often both possible datastructures in situations where an unknown number of data elements must be stored. However, there are some rules of thumb to follow when a choice between the two datastructures must be made.



Other considerations related to the choice between lists and vectors should also be given some thought. Although it is true that the vector is able to grow dynamically, the dynamical growth does involve a lot of copying of data elements. Clearly, copying a million large datastructures takes a considerable amount of time, even on fast computers. On the other hand, inserting a large number of elements in a list doesn't required us to copy the remainder of the list structure: inserting a new element in a list merely requires us to juggle some pointers around. In figure 6 this is shown: a new element is inserted between the second and third element, creating a new list of four elements.

figure 6 is shown here.
figure 6: Adding a new element to a list


Removing an element from a list also is a simple matter. Starting again from the situation shown in figure 5, figure 7 shows what happens if element two is removed from our list. Again: only pointers need to be juggled. In this case it's even simpler than adding an element: only two pointers need to be rerouted.

figure 7 is shown here.
figure 7: Removing an element from a list



Summarizing the comparison between lists and vectors, it's probably best to comclude 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 if worse comes to worst, a profiler may be required to find out what's working best. And even then....


But, no matter what thoughts remain, the list container is available, so let's see what we can do with it. As with the vector-class, the following constructors and memberfunctions are available:
Constructors:


Note that a list may be defined without size:

list<int> ivect;
This defines an empty list, without any element at all. So, a statement like
ivect[0] = 18;
would, in this case, be an error, as there isn't as yet any element. In this case, the preferred idiom is:
ivect.push_back(18);


Memberfunctions that were also available in vector:


Also available are:

Note that the merge() and sort() members both assume the availability of the < and == operators.


Available operators with the list containertype are:


7.2.3: The `queue' container

The queue class implements a queue datastructure. To use the queue, the header file queue must be included:

            #include <queue>
 


A queue is depicted in figure 8.

figure 8 is shown here.
figure 8: A queue data-structure


In figure 8 it is shown that a queue has one point (the back) where items can be added to the queue, and one point (the front) where items can be removed (read) from the queue.


Bearing this model of the queue in mind, let's see what we can do with it.


A queue can be initialized by an existing other queue, or it can be created empty:


        queue<int>
            queue1;
        ...
        queue<int>
            queue2(queue1);
 


Apart from these constructors, and the basis operators for comparison and assignment (see the introductory paragraph of this chapter), the following memberfunctions are available:



Note that the queue does not support iterators or a subscript operator. The only elements that can be accesses are its front and back element, and it can only be emptied by repeatedly removing its front element.


7.2.4: The `priority_queue' container

The priority_queue class implements a priority_queue datastructure. To use the priority_queue, the header file queue must be included:

            #include <queue>
 


A priority_queue is identical to a queue, but allows the entry of data-elements according to priority rules. An example of a situation where the priority queue is encountered in real-life is found at the check-in terminals at airports. At a terminal the passengers normally stand in line to wait for their turn to check in, but late passengers (e.g., passengers checking in less than 15 minutes before their plane is scheduled to depart) are usually allowed to jump the queue: they receive a higher priority than the other passengers.


The priority queue uses the <-operator of the used data type to decide about the priority of the data elements. The smaller the value, the lower the priority. So, the priority queue could also be used for sorting values while they arrive.


A simple example of a priority_queue application is the following program: it reads words from cin and writes a sorted list of words to cout:

#include <iostream>
#include <string>
#include <queue>

int main()
{
    priority_queue<string>
        q;

    string
        word;

    while (cin >> word)
        q.push(word);

    while (q.size())
    {
        cout << q.top() << endl;
        q.pop();
    }

    return (0);
}


Unfortunately, the words are listed in reversed order: because of the underlying <-operator the words appearing later in the ascii-sequence appear first in the priority queue. A solution for that problem is to define a wrapper class around the string datatype, in which the <-operator has been defined according to our wish, i.e., making sure that the words appearing early in the ascii-sequence appear first in the queue. Here is the modified program:

#include <iostream>
#include <string>
#include <queue>

class Text
{
    public:
        Text(string const &str): s(str) 
        {}
        operator string const &() const
        {
            return (s);
        }
        bool operator<(Text const &right) const
        {
            return (s > right.s);
        }
    private:
        string
            s;
};

ostream &operator<<(ostream &ostr, Text const &text)
{
    return (ostr << text);
}

int main()
{
    priority_queue<Text>
        q;
    string
        word;
    
    while (cin >> word)
        q.push(word);

    while (q.size())
    {
        word = q.top();
        cout << word << endl;
        q.pop();
    }
    return (0);
}
In the above program the wrapper class defines the operator< just the other way around than the string class itself, resulting in the preferred ordering. Other possibilities would be to store the contents of the priority queue in, e.g., a vector, from which the elements can be read in reversed order. However, the example shows how the priority_queue can be fed objects of a special class, in which the operator< has been tailored to a particular use.


A priority_queue can be initialized by an existing other priority_queue, or it can be created empty:


        priority_queue<int>
            priority_queue1;
        ...
        priority_queue<int>
            priority_queue2(priority_queue1);
 


Apart from these constructors, and the basis operators for comparison and assignment (see the introductory paragraph of this chapter), the following memberfunctions are available:



Note that the priority_queue does not support iterators or a subscript operator. The only element that can be accesses is its top element, and it can only be emptied by repeatedly removing this element.



7.2.5: The `deque' container

The deque class implements a deque datastructure. To use the deque, the header file deque must be included:

            #include <deque>
 


A deque is a double ended queue. It's comparable to a queue, but allows reading and writing at both ends of the queue. Actually, the deque data type supports a lot more functionality than the queue, as will be clear from the following overview of memberfunctions that are available for the deque:


First, several constructors are available for the deque:


To access the individual elements of the deque, the following members are available:


Operations affecting all elements of a deque:


Elements may be added and removed from both ends of a deque:


Elements may also inserted somewhere within the deque:


Apart from using resize(), elements may be removed from the deque as follows:


7.2.6: The `map' container

The map class implements a (sorted) associative array. To use the map, the header file map must be included:

            #include <map>
 
A map is filled with Key/Value pairs, which may be of any container-acceptable type The key is used for looking up the information belonging to the key. The information is the Value. For example, a phonebook uses the names of people as the key, and uses the telephone number and maybe other information (e.g., the zip-code, the address, the profession) as value.


Basically, the operations on a map are the storage of Key/Value combinations, and looking for a value, given a key. Each key can be stored only once in a map. If the same key is entered twice, the key/value pair that is last entered is stored, and the pair that was first entered is lost.


A single value that must be entered into a map must be constructed. For this, a map defines a value_type, corresponding to a particular maptype, which may be used to create values of that type. For example, with a map<string, int> it can be used as follows:


        map<string, int>::value_type(string("Hello"), 1)
 
Instead of using the line map<string, string>::value_type(...) over and over again, a typedef may come in handy here:

        typedef map<string, string>::value_type MapSSValue 
 
Following this typedef, values for the map<string, string> may be constructed as

        MapSSValue(string("Hello"), 1)
 


Apart from the basic operations (assignment, comparison, etc,), the map supports several more operations. Here they are:



The standard iterators are available:


Other available memberfunctions are:

The following members have special meanings with the multimap, but they are defined with the plain map too:


7.2.7: The `multimap' container

Like the map, the multimap class implements also a (sorted) associative array. To use the multimap, the header file multimap must be included:

            #include <multimap>
 
The main difference between the map and the multimap is that the multimap supports multiple entries of the same key, whereas the map contains only unique keys. Note that multiple entries of the same key and the same value are also accepted.


The functions that are available with the multimap and the map are practically the same, with the exception of the subscript operator ([]), which is not supported with the multimap. This is understandable: if multiple entries of the same key are allowed, which of the possible values should be returned for myMap[myKey]?


Below the available constructors and memberfunctions are mentioned. They are presented without further comment if their function is identical to that of the map container.


A single value that is to be entered into a multimap must be constructed. For this, a multimap defines a value_type, corresponding to a particular multimap type, which may be used to create values of that type. For example, with a multimap<string, string> it can be used as follows:


        multimap<string, string>::value_type(string("Hello"), 1)
 


Here are the constructors that are available for the multimap:

The standard iterators:


Available memberfunctions:



7.2.8: The `set' container

The set class implements a set of (sorted) values. To use the set, the header file set must be included:

            #include <set>
 
A set is filled with values, which may be of any container-acceptable type. Each value can be stored only once in a set.


A single value that is to be entered in a set must be constructed. For this, a set defines a value_type, corresponding to a particular type of set, which may be used to create values of that type. For example, with a set<string> it can be used as follows:


        set<string>::value_type(string("Hello"))
 
Instead of using the line set<string>::value_type(...) over and over again, a typedef may come in handy here:

        typedef set<string>::value_type SetSValue 
 
Following this typedef, values for the set<string, string> may be constructed as

        SetSValue(string("Hello"))
 


Apart from the basic operations (assignment, comparison, etc,), the set supports several more operations. Here they are:



The standard iterators are available:


Other available memberfunctions are:

The following members have special meanings with the multiset, but they are defined with the plain set too:



7.2.9: The `multiset' container

Like the set, the multiset class implements also a (sorted) set of values. To use the multiset, the header file multiset must be included:

            #include <multiset>
 
The main difference between the set and the multiset is that the multiset supports multiple entries of the same value, whereas the set contains only unique values.


The functions that are available with the multiset and the set are the same. Below the available constructors and memberfunctions are mentioned. They are presented without further comment if their function is identical to that of the set container.


A single value that is to be entered into a multiset must be constructed. For this, a multiset defines a value_type, corresponding to a particular multiset type, which may be used to create values of that type. For example, with a multiset<string> it can be used as follows:


        multiset<string>::value_type(string("Hello"))
 


Here are the constructors that are available for the multiset:

The standard iterators:


Available memberfunctions:


A small example showing the use of various memberfunctions of a multiset is given below:

    #include <string>
    #include <set>
    #include <iostream>
    
    int main()
    {
        string
            sa[] = 
            {
                "alfa", 
                "echo", 
                "hotel", 
                "mike", 
                "romeo"
            };
     
        multiset<string>
            xset(&sa[0], &sa[5]);
    
        xset.insert(multiset<string> ::value_type("echo"));
        xset.insert(multiset<string> ::value_type("echo"));
        xset.insert(multiset<string> ::value_type("echo"));
    
        multiset<string>::iterator
            it = xset.find("echo");
    
        for (; it != xset.end(); ++it)
            cout << *it << " ";
        cout << endl;
    
        pair
        <
            multiset<string>::iterator,
            multiset<string>::iterator
        >
            itpair = xset.equal_range("echo");
    
        for (; itpair.first != itpair.second; ++itpair.first)
            cout << *itpair.first << " ";
    
        cout << endl << 
                xset.count("echo") << " occurrences of 'echo'" << endl;
    
    
        return (0);
    }


7.2.10: The `stack' container

The stack class implements a stack datastructure. To use the stack, the header file stack must be included:

            #include <stack>
 


A stack is also called a first-in last-out datastructure, as the first item to enter the stack is the last item that will be removed from the stack. A stack is an extremely useful datastructure in situations where data must be temporarily be available. For example, programs maintain a stack to store local variables of functions: these variables live only as long as the functions live, contrary to global (or static local) variables, which live for as long as the program itself lives. Another example is found in calculators using the Reverse Polish Notation (RPN), in which the operands of expressions are entered in the stack, and the operators pop their operands and push the results of their work.


As an example of the use of a stack, consider figure 9, in which the contents of the stack is shown while the expression (3 + 4) * 2 is evaluated. In the RPN this expression becomes 3 4 + 2 *, and figure 9 shows the stack contents after each token (i.e., the operands and the operators) is read from the input. Notice that indeed each operand is pushed on the stack, while each operator changes the contents of the stack.

figure 9 is shown here.
figure 9: The contents of a stack while evaluating 3 4 + 2 *


The expression is evaluated in five steps. The caret between the tokens in the expressions shown on the first line of figure 9 shows what token has just been read. The next line shows the actual stack-contents, and the 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 at the bottom of the stack. Next, in step 3, the + operator is read. The operator pops two operands (so that the stack is empty at that moment), calculates their sum, and pushes the resulting value (7) on the stack. Then, in step 4, the number 2 is read, which is dutifully pushed on the stack again. Finally, in step 5 the final operator * is read, which pops the values 2 and 7 from the stack, computes their product, and pushes the result back on the stack. This result (14) could then be popped to be displayed on some medium.


From figure 9 we see that a stack has one point (the top) where items can be added to and removed from the stack. Furthermore, values can be pushed and popped from a stack.


Bearing this model of the stack in mind, let's see what we can formally do with it, using the stack container.


A stack can be initialized by an existing other stack, or it can be created empty:


        stack<int>
            stack1;
        ...
        stack<int>
            stack2(stack1);
 


Apart from these constructors, and the basis operators for comparison and assignment (see the introductory paragraph of this chapter), the following memberfunctions are available:



Note that the stack does not support iterators or a subscript operator. The only elements that can be accesses is its top element, and it can only be emptied by repeatedly popping the element at the top.


7.2.11: The `hash_map' and other hashing-based containers

The (multi) map and (multi) set containertypes store sorted keys. This is in general not the fastest way to store keys with respect to storage and retrieval. The main benefit of sorted keys is that a listing of sorted keys appeals more to humans than an unsorted list. However, a by far faster method of storing keys is to use hashing. Hashing uses a function (called the hash-function) to compute a (unsigned) number from the key, which number is thereupon used as an index in the table in which the keys are stored. Retrieval of a key is as simple as computing the hashvalue of the provided key, and looking at the table in the computed indexlocation: if the key is present, it is stored in the table, and its value can be returned. If it's not present, the key is not stored.


Boundary conditions arise when an index position is already occupied. For these situations the abstract containers have solutions available, but that topic is beyond the subject of this chapter.


Although the hash methodology is extremely powerful, there is no official support for it in the abstract container types of the standard template library. Nonetheless, unofficial support exists, and it's likely that in due time containers based on hashing are added to the already available abstract containers.


The egcs compiler does support the hash_(multi)map and hash_(multi)set containers. Below the hash_map container is illustrated. The other containers using hashing (hash_multimap, hash_set and hash_multiset) operate correspondingly.


Hash functions are available for char const * variables, and for all the scalar numerical types char, short, int etc.. If another datatype must be used, two classes supporting function objects must be constructed. One class is called, e.g., equal. Its function-call compares two variables of the key datatype, returning true if they are equal, and false otherwise. The other class is called hash. Its function-call returns the hashvalue of the variable of the same type as the key, which is passed as its argument.


In the following example a hash_map is defined for a string, int combination. A class equalString is defined comparing two string variables, and a template specialization for the class hash is defined. The how and why of template specializations are covered in chapter 16. The equalString class is constructed as follows:


        class equalString
        {
            public:
                bool operator()(string const &s1, string const &s2) const
                {
                    return (s1 == s2);
                }
        };
    
The hash<string> explicit specialization in fact uses the predefined hash<char const *> template, but the roundabout way is chosen here to illustrate how an template explicit specialization can be constructed:

        template <>
        class hash<string>
        {
            public:
                size_t operator()(string const &str) const
                {
                    hash<char const *>
                        h;
                    return (h(str.c_str()));
                }
        };
    
The complete example program defines a map containing the 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 months are displayed:

        #include <iostream>
        #include <string>
        #include <hash_map>
        
        class equalString;      // insert the code of these 
                                // two classes here
        template <> class hash<string>;

        int main()
        {
            hash_map<string, int, hash<string>, equalString>
                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"] << endl <<
                    "april     -> " << months["april"] << endl <<
                    "june      -> " << months["june"] << endl <<
                    "november  -> " << months["november"] << endl;
        
            return (0);
        }
    


The hash_map class implements a associative array in which the key is stored according to some hashing scheme. To use the hash_map, the header file hash_map must be included:


            #include <hash_map>
    


Like the map, a single value that will be entered into a hash_map must be constructed. For this, a hash_map defines a value_type, corresponding to a particular hash_map-type, which may be used to create values of that type. For example, with a hash_map<string, int> it can be used as follows:


        hash_map<string, int>::value_type(string("Hello"), 1)
 


All the memberfunctions and constructors that are available for the map datatype can also be used for the hash_map. The constructor target(n) defines a hash_map consisting of an initial number of n slots to put key/value combinations in. This number is automatically extended when needed.


The hash_multimap, hash_set and hash_multiset containers are used analogously. For these containers the equal and hash classes must also be defined. The hash_multimap also requires the hash_map header file, the hash_set and hash_multiset containers can be used after including the hash_set header file. Be careful not to use the subscript operator with the hash_multimap and hash_multiset, as this operator is not defined for the multi_... containers.



7.3: The `complex' container

The complex container is a specialized container in that it defines operations that can be performed on complex numbers, given possible numerical real and imaginary data types.


In order to use the complex container, the headerfile


            #include <complex>
    
must be included.


The complex container can be used to define complex numbers, consisting of two parts, representing the real and complex parts of a complex number.


While initializing (or assigning) a complex variable, the imaginary part may be left out of the initialization or assignment, in which case this part is 0 (zero). By default, both parts are zero.


When complex numbers are defined, the typedefinition requires the specification of the datatype of the real and imaginary parts. E.g.,


            complex<double>
            complex<int>        
            complex<float>      
    
Note that the real and imaginary parts of complex numbers have the same datatypes.


Below it is silently assumed that the used complex type is complex<double>. Given this assumption, complex numbers may be initialized as follows:

Anonymous complex values may also be used. In the following example two anonymous complex values are pushed on a stack of complex numbers, to be popped again thereafter:
#include <iostream>
#include <complex>
#include <stack>

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" << endl;
        cstack.pop();
    }

    return (0);
}

Note that a blank is required between the two consecutive >-barckets used in the definition of cstack. If the blank is omitted, the resulting >> is read as the right-shift operator, which of course makes no sense here.


The following memberfunctions and operators are defined for complex numbers:

Furthermore, several mathematical functions are available for complex numbers. They are abs(), arg(), conj(), cos(), cosh(), exp(), log(), norm(), polar(), pow(), sin(), sinh()) and sqrt(). These functions are normal functions, not memberfunctions. They accept complex numbers as their arguments. For example,

            abs(complex<double>(3, -5));
            pow(target, complex<int>(2, 3));
    


Complex numbers may be extracted from istream objects and inserted into ostream objects. The insertion results in an ordered pair (x, y), in which x represents the real part and y the imaginary part of the complex number. The same form may also be used when extracting a complex number from an istream object. However, simpler forms are also allowed. E.g., 1.2345: only the real part, the imaginary part will be set to 0; (1.2345): the same value.


Finally, ordinary numbers may be used in expressions involving complex numbers. E.g.,


            // assume target is complex<double>:
            target *= 3;
    
Note, however, that the reverse does not hold true: a complex number cannot be assigned to a non-complex type variable. In these situations the real(), imag() or other functions must be used. E.g.:

            // assume x is double:
            x = target;         // error: x is not complex<double>
            x = target.real();  // ok.