9.1. Defining a Sequential ContainerWe already know a fair bit about how to use the sequential containers based on what we covered in Section 3.3 (p. 90). To define a container object, we must include its associated header file, which is one of #include <vector> #include <list> #include <deque> Each of the containers is a class template (Section 3.3, p. 90). To define a particular kind of container, we name the container followed by angle brackets that enclose the type of the elements the container will hold: vector<string> svec; // empty vector that can hold strings list<int> ilist; // empty list that can hold ints deque<Sales_item> items; // empty deque that holds Sales_items Each container defines a default constructor that creates an empty container of the speicfied type. Recall that a default constructor takes no arguments.
9.1.1. Initializing Container ElementsIn addition to defining a default constructor, each container type also supports constructors that allow us to specify initial element values.
Intializing a Container as a Copy of Another ContainerWhen we initialize a sequential container using any constructor other than the default constructor, we must indicate how many elements the container will have. We must also supply initial values for those elements. One way to specify both the size and element values is to initialize a new container as a copy of an existing container of the same type: vector<int> ivec; vector<int> ivec2(ivec); // ok: ivec is vector<int> list<int> ilist(ivec); // error: ivec is not list<int> vector<double> dvec(ivec); // error: ivec holds int not double
Initializing as a Copy of a Range of ElementsAlthough we cannot copy the elements from one kind of container to another directly, we can do so indirectly by passing a pair of iterators (Section 3.4, p. 95). When we use iterators, there is no requirement that the container types be identical. The element types in the containers can differ as long as they are compatible. It must be possible to convert the element we copy into the type held by the container we are constructing. The iterators denote a range of elements that we want to copy. These elements are used to initialize the elements of the new container. The iterators mark the first and one past the last element to be copied. We can use this form of initialization to copy a container that we could not copy directly. More importantly, we can use it to copy only a subsequence of the other container: // initialize slist with copy of each element of svec list<string> slist(svec.begin(), svec.end()); // find midpoint in the vector vector<string>::iterator mid = svec.begin() + svec.size()/2; // initialize front with first half of svec: The elements up to but not including *mid deque<string> front(svec.begin(), mid); // initialize back with second half of svec: The elements *mid through end of svec deque<string> back(mid, svec.end()); Recall that pointers are iterators, so it should not be surprising that we can initialize a container from a pair of pointers into a built-in array: char *words[] = {"stately", "plump", "buck", "mulligan"}; // calculate how many elements in words size_t words_size = sizeof(words)/sizeof(char *); // use entire array to initialize words2 list<string> words2(words, words + words_size); Here we use sizeof (Section 5.8, p. 167) to calculate the size of the array. We add that size to a pointer to the first element to get a pointer to a location one past the end of the array. The initializers for words2 are a pointer to the first element in words and a second pointer one past the last element in that array. The second pointer serves as a stopping condition; the location it addresses is not included in the elements to be copied. Allocating and Initializing a Specified Number of ElementsWhen creating a sequential container, we may specify an explicit size and an (optional) initializer to use for the elements. The size can be either a constant or non-constant expression. The element initializer must be a valid value that can be used to initialize an object of the element type: const list<int>::size_type list_size = 64; list<string> slist(list_size, "eh?"); // 64 strings, each is eh? This code initializes slist to have 64 elements, each with the value eh?. As an alternative to specifying the number of elements and an element initializer, we can also specify only the size: list<int> ilist(list_size); // 64 elements, each initialized to 0 // svec has as many elements as the return value from get_word_count extern unsigned get_word_count(const string &file_name); vector<string> svec(get_word_count("Chimera")); When we do not supply an element initializer, the library generates a value-initialized (Section 3.3.1, p. 92) one for us. To use this form of initialization, the element type must either be a built-in or compound type or be a class type that has a default constructor. If the element type does not have a default constructor, then an explicit element initializer must be specified.
9.1.2. Constraints on Types that a Container Can HoldWhile most types can be used as the element type of a container, there are two constraints that element types must meet:
There are additional constraints on the types used as the key in an associative container, which we'll cover in Chapter 10. Most types meet these minimal element type requirements. All of the built-in or compound types, with the exception of references, can be used as the element type. References do not support assignment in its ordinary meaning, so we cannot have containers of references. With the exception of the IO library types (and the auto_ptr type, which we cover in Section 17.1.9 (p. 702)), all the library types are valid container element types. In particular, containers themselves satisfy these requirements. We can define containers with elements that are themselves containers. Our Sales_item type also satisifes these requirements. The IO library types do not support copy or assignment. Therefore, we cannot have a container that holds objects of the IO types. Container Operations May Impose Additional RequirementsThe requirement to support copy and assignment is the minimal requirement on element types. In addition, some container operations impose additional requirements on the element type. If the element type doesn't support the additional requirement, then we cannot perform that operation: We can define a container of that type but may not use that particular operation. One example of an operation that imposes a type constraint is the constructors that take a single initializer that specifies the size of the container. If our container holds objects of a class type, then we can use this constructor only if the element type has a default constructor. Most types do have a default constructor, although there are some classes that do not. As an example, assume that Foo is a class that does not define a default constructor but that does have a constructor that takes an int argument. Now, consider the following declarations: vector<Foo> empty; // ok: no need for element default constructor vector<Foo> bad(10); // error: no default constructor for Foo vector<Foo> ok(10, 1); // ok: each element initialized to 1 We can define an empty container to hold Foo objects, but we can define one of a given size only if we also specify an initializer for each element. As we describe the container operations, we'll note the constraints, if any, that each container operation places on the element type. Containers of ContainersBecause the containers meet the constraints on element types, we can define a container whose element type is itself a container type. For example, we might define lines as a vector whose elements are a vector of strings: // note spacing: use ">>" not ">>" when specifying a container element type vector< vector<string> > lines; // vector of vectors Note the spacing used when specifying a container element type as a container: vector< vector<string> > lines; // ok: space required between close > vector< vector<string>> lines; // error: >> treated as shift operator
![]() |