0% found this document useful (0 votes)
20 views12 pages

STL Vector

Ótima explicação Nicolay Josuttis sobre a classe vector com exemplos

Uploaded by

custodio
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
20 views12 pages

STL Vector

Ótima explicação Nicolay Josuttis sobre a classe vector com exemplos

Uploaded by

custodio
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 12
270 Chapter 7: STL Containers 7.3 Vectors ‘A vector models a dynamic array. Thus, a vector is an abstraction that manages its elements with a dynamic C-style array (Figure 7.2). However, the standard does not specify that the implementation uses a dynamic array. Rather, this follows from the constraints and specification of the complexity of its operation, Figure 7.2. Structure of a Vector To use a vector, you must include the header file : #include There, the type is defined as a class template inside namespace sta: namespace std { template > class vector; 3 The elements of a vector may have any type T. The optional second template parameter defines the memory model (see Chapter 19). The default memory model is the model allocator, which is provided by the C+ standard library. 7.3.1 Abil ‘A vector copies its elements into its internal dynamic array. The elements always have a certain order, Thus, a vector is a kind of ordered collection. A vector provides random access. Thus, you can access every element directly in constant time, provided that you know its position. The iterators are random-access iterators, so you can use any algorithm of the STL. Vectors provide good performance if you append or delete elements at the end. If you insert or delete in the middle or at the beginning, performance gets worse. This is because every element behind has to be moved to another position. In fact, the assignment operator would be called for every following clement. Size and Capacity Part of the way in which vectors give good performance is by allocating more memory than they need to contain all their elements. To use vectors effectively and correctly, you should understand how size and capacity cooperate in a vector. 7.3 Vectors 211 Vectors provide the usual size operations size(), empty (), and max_size() (see Section 7.1.2, page 254), An additional “size” operation is the capacity () function, which returns the number of elements a vector could contain in its actual memory. If you exceed the capacity (), the vector has to reallocate its internal memory. ‘The capacity of a vector is important for two reasons: 1. Reallocation invalidates all references, pointers, and iterators for elements of the vector. 2. Reallocation takes time. Thus, if a program manages pointers, references, or iterators into a vector, or if speed is a goal, it is important to take the capacity into account. To avoid reallocation, you can use reserve() to ensure a certain capacity before you really need it. In this way, you can ensure that references remain valid as long as the capacity is not exceeded: std::vector v; — //create an empty vector v.reserve(80); reserve memory for 80 elements Another way to avoid reallocation is to initialize a vector with enough elements by passing additional arguments to the constructor. For example, if you pass a numeric value as parameter, it is taken as the starting size of the vector: std::vector v(S); — /creates a vector and initializes it with five values 1 (calls five times the default constructor of type T) Of course, the type of the elements must provide a default constructor for this ability. For fundamen- tal types, zero initialization (see Section 3.2.1, page 37) is guaranteed. But note that for complex types, even if a default constructor is provided, the initialization takes time. If the only reason for initialization is to reserve memory, you should use reserve(). ‘The concept of capacity for vectors is similar to that for strings (see Section 13.2.5, page 669), with one big difference: Unlike for strings, it is not possible to call reserve () for vectors to shrink the capacity, Calling reserve() with an argument that is less than the current capacity is a no-op. Furthermore, how to reach an optimal performance regarding speed and memory use is implementa- tion defined, Thus, implementations might increase capacity in larger steps. In fact, to avoid internal fragmentation, many implementations allocate a whole block of memory (such as 2K) the first time you insert anything if you don’t call reserve() first yourself, This can waste a lot of memory if you have many vectors with only a few small elements Because the capacity of vectors never shrinks, it is guaranteed that references, pointers, and iterators remain valid even when elements are deleted, provided that they refer to a position before the manipulated elements. However, insertions invalidate all references, pointers, and iterators when the capacity gets exceeded. C++11 introduced a new member function for vectors: a nonbinding request to shrink the capac- ity to fit the current number of elements: veshrinkto_£it(); — /request to shrink memory (since C++11) This request is nonbinding to allow latitude for implementation-specific optimizations. Thus, you cannot expect that afterward v.capacity==v.size() yields true. Before C+#11, there you could shrink the capacity only indirectly: Swapping the contents with another vector swaps the capacity. The following function shrinks the capacity while preserving the elements: 272 Chapter 7: STL Containers template void shrinkCapacity(std: :vector& v) t std::vector tmp(v 1 copy elements into a new vector v.swap(tmp) ; / swap internal vector data } ‘You could even shrink the capacity without calling this function by calling the following statement:5 1/ shrink capacity of vector v for type T std: :vector(v) .swap(v); However, note that after swap (), all references, pointers, and iterators swap their containers. They still refer to the elements to which they referred on entry. Thus, shrinkCapacity() invalidates all references, pointers, and iterators. The same is true for shrink_to_fit (). Operation Effect vector ¢ Default constructor, creates an emply vector without any elements vector ¢(c2) Copy constructor; creates a new vector as a copy of e2 (all elements are copied) vector ¢ = c2 Copy constructor; creates a new vector as a copy of c2 (all elements are copied) vector ¢(r) Move constructor; creates a new vector, taking the contents of the rvalue rv (since C++11) vector ¢ = rv Move constructor; creates a new vector, taking the contents of the rvalue rv (since C+411) vector c(n) Creates a vector with n elements created by the default constructor vector c(n,elem) | Creates a vector initialized with n copies of clement elem vector c(beg,end) | Creates a vector initialized with the elements of the range [beg.end) vector c(initlist) | Creates a vector initialized with the elements of initializer list initlist (since C++11) initlist | Creates a vector initialized with the elements of initializer list initlist (since C++11) c."vector() Destroys all elements and frees the memory vector Table 7.9. Constructors and Destructor of Vectors * You (or your compiler) might consider this statement as being incorrect because it calls a nonconstant member function for a temporary value, However, standard C++ allows you to call a nonconstant member function for temporary values. 7.3 Vectors 273 7.3.2. Vector Operations Create, Copy, and Destroy Table 7.9 lists the constructors and destructors for vectors. You can create vectors with and without elements for initialization. If you pass only the size, the elements are created with their default constructor. Note that an explicit call of the default constructor also initializes fundamental types, such as int, with zero (see Section 3.2.1, page 37). See Section 7.1.2, page 254, for some remarks about possible initialization sources. Nonmodifying Operations Table 7.10 lists all nonmodifying operations of vectors. See additional remarks in Section 7.1.2, page 254, and Section 7.3.1, page 270. Operation Effect e.euptyO Returns whether the container is empty (equivalent to sizeQ) might be faster) c.size() Returns the current number of elements c.max_size() Returns the maximum number of elements possible c.capacity() Returns the maximum possible number of elements without reallocation c.reserve(num) | Enlarges capacity, if not enough yet® c.shrink_to_fit() | Request to reduce capacity to fit number of elements (since C++11)® ct == 2 Returns whether c1 is equal to ¢2 (calls == for the elements) ct != 2 Retums whether c1 is not equal to ¢2 (equivalent to ! (ct==¢2)) ct < 2 Retums whether c1 is less than ¢2 cl > cB Returns whether cf is greater than ¢2 (equivalent to c2= 2 Returns whether cf is greater than or equal to ¢2 (equivalent to '(el 1; std: :vector coll; MU make coll be a copy of the contents of L coll.assign(1.begin() ,1.end()); Element Access To access all elements of a vector, you must use range-based for loops (see Section 3.1.4, page 17), specific operations, or iterators. Table 7.12 shows all vector operations for direct element access. AS usual in C and C++, the first element has index 0, and the last element has index size()-1. Thus, the nth element has index n-1. For nonconstant vectors, these operations return a reference to the element, Thus, you could modify an element by using one of these operations, provided it is not forbidden for other reasons. Operation | Effect elidel Retums the clement with index idx (no range checking) c.at(idx) | Returns the element with index idx (throws range-error exception if idx is out of range) ¢.front() | Returns the first element (no check whether a first element exists) c.back() _| Returns the last element (no check whether a last element exists) Table 7.12. Direct Element Access of Vectors The most important issue for the caller is whether these operations perform range checking. Only at() performs range checking. If the index is out of range, at () throws an out_of_range 7.3 Vectors 275 exception (see Section 4.3, page 41). Alll other functions do not check. A range error results in undefined behavior. Calling operator [], front (), and back() for an empty container always results in undefined behavior: std: :vector coll; Mempty! coll[5] = elem; 1/ RUNTIME ERROR => undefined behavior std::cout << coll.front(); | //RUNTIME ERROR = undefined behavior So, you must ensure that the index for operator (/] is valid and that the container is not empty when either front () or back() is called: std: :vector coll; Hempty! if (coll.size() > 5) { coll[5] = elem; HOK + if (Icoll.empty()) { cout << coll.front(); | / OK } coll.at(5) = elem; M throws out_of range exception Note that this code is OK only in single-threaded environments. In multithreaded contexts, you need synchronization mechanisms to ensure that co11 is not modified between the check for its size and the access to the element (see Section 18.4.3, page 984, for details). Iterator Functions Vectors provide the usual operations to get iterators (Table 7.13). Vector iterators are random-access iterators (see Section 9.2, page 433, for a discussion of iterator categories). Thus, in principle you could use all algorithms of the STL. ‘The exact type of these iterators is implementation defined. For vectors, however, the iterators retumed by begin(), cbegin(), end(), and cend() are often ordinary pointers, which is fine because vectors usually use a C-style array for the elements and ordinary pointers provide the inter- face of random-access iterators. However, you can’t count on the fact that the iterators are ordinary pointers. For example, if a safe version of the STL that checks range errors and other potential prob- lems is used, the iterator type is usually an auxiliary class. See Section 9.2.6, page 440, for a nasty difference between iterators implemented as pointers and iterators implemented as classes. Iterators remain valid until an element with a smaller index gets inserted or removed or until reallocation occurs and capacity changes (see Section 7.3.1, page 270) Inserting and Removing Elements Table 7.14 shows the operations provided for vectors to insert or to remove elements. As usual when using the STL, you must ensure that the arguments are valid. Iterators must refer to valid positions, and the beginning of a range must have a position that is not behind the end, 276 Chapter 7: STL Containers ‘Operation _| Effect e.begin®) | Returns a random-access iterator for the first clement c.end() Returns a random-access iterator for the position after the last element c.cbegin() | Returns a constant random-access iterator for the first element (since C+11) c.cend() Returns a constant random-access iterator for the position after the last element (since C+411) c.rbegin() | Returns a reverse iterator for the first element of a reverse iteration c.rend() Returns a reverse iterator for the position after the last element of a reverse iteration c.crbegin() | Retums a constant reverse iterator for the first element of a reverse iteration (since C+411) c.crend() | Returns a constant reverse iterator for the position after the last element of a reverse iteration (since C+#11) Table 7.13. Iterator Operations of Vectors As usual, it is up to the programmer to ensure that the container is not empty when pop_back() is called. For example: std: :vector coll; Mempty! coll.pop_back() ; // RUNTIME ERROR => undefined behavior if (Icoll.empty()) { coll. pop_back(); WOK 3 However, note that in a multithreaded context you have to ensure that col doesn’t get modified between the check for being empty and pop_back() (see Section 18.4.3, page 984). Regarding performance, you should consider that inserting and removing happens faster when e Elements are inserted or removed at the end. © The capacity is large enough on entry. © Multiple elements are inserted by a single call rather than by multiple calls, Inserting or removing elements invalidates references, pointers, and iterators that refer to the follow- ing elements, An insertion that causes reallocation invalidates all references, iterators, and pointers, Vectors provide no operation to remove elements directly that have a certain value. You must use an algorithm to do this. For example, the following statement removes all elements that have the value val: sti vector coll; 1 remove all elements with value val coll.erase(remove(coll.begin() ,coll.end() , val), coll.end()); This statement is explained in Section 6.7.1, page 218 7.3 Vectors 271 Operation Effect ¢. push_back (elem) c.pop_back () c.insert (pos, elem) c. insert (pos,n, elem) c.insert (pos, beg ,end) c.insert (pos, initlist) c.emplace(pos,args. c.emplace_back (args...) c.erase (pos) c.erase (beg ,end) c.resize (num) c.resize (num, elem) c.clear() “Appends a copy of elem at the end Removes the last element (does not return it) Inserts a copy of elem before iterator position pos and returns the position of the new element Inserts n copies of elem before iterator position pos and returns the position of the first new element (or pos if there is no new element) Inserts a copy of all elements of the range [beg,end) before iterator position pos and returns the position of the first new element (or pos if there is no new element) Inserts a copy of all elements of the initializer list initlist before iterator position pos and returns the position of the first new element (or pos if there is no new element; since C++11) Inserts a copy of an element initialized with args before iterator position pos and returns the position of the new element (since C++11) Appends a copy of an element initialized with args at the end (returns nothing; sinee C++11) Removes the element at iterator position pos and returns the position of the next element Removes all elements of the range [beg,end) and returns the position of the next element Changes the number of elements to num (if size() grows new elements are created by their default constructor) Changes the number of elements to num (if size() grows new elements are copies of elem) Removes all elements (empties the container) Table 7.14, To remove only the first element that has a certain value, you must use the following statement std: :vector col: Insert and Remove Operations of Vectors MW remove first element with value val std: :vector pos = find(coll.begin() val); if (pos coll.erase(pos) ; terator pos; ,coll.end(), coll.end()) { 278 Chapter 7: STL Containers 7.3.3 Using Vectors as C-Style Arrays As for class array<>, the C++ standard library guarantees that the elements of a vector are in contiguous memory. Thus, you can expect that for any valid index 4 in vector v, the following yields true: &v(i] == evlo] +i This guarantee has some important consequences. It simply means that you can use a vector in all cases in which you could use a dynamic array. For example, you can use a vector to hold data of ordinary C-strings of type char* or const char*: std: :vector v; UW create vector as dynamic array of chars v.resize(41) ; MW make room for 41 characters (including *\0?) strepy(kv[0],"hello, world"); /copy a C-string into the vector printé("%s\n", &v[0]); 1/ print contents of the vector as C-string Note, however, that since C++11, you don’t have to use the expression & [0] to get direct access to the elements in the vector, because the member function data () is provided for this purpose: sti vector v; UW create static array of 41 chaxs strepy(v.data() "hello, world"); //copy a C-string into the array print#("%s\n", v.data()); 1 print contents of the array as C-string Of course, you have to be careful when you use a vector in this way (just as you always have to be careful when using ordinary C-style arrays and pointers). For example, you have to ensure that the size of the vector is big enough to copy some data into it and that you have an *\0? element at the end if you use the contents as a C-string. However, this example shows that whenever you need an array of type T for any reason, such as for an existing C library, you can use a vector and pass the address of the first element. Note that you must not pass an iterator as the address of the first element. Iterators of vectors have an implementation-specific type, which may be totally different from an ordinary pointer: \n", v.begin()); // ERROR (might work, but not portable) \n", v.data()); WOK (since C++11) s\n", &v[0]) 5 OK, but data() is better 7.3.4 Exception Handling ‘Vectors provide only minimal support for logical error checking. The only member function for which the standard requires that it may throw an exception is at (), which is the safe version of the subscript operator (see Section 7.3.2, page 274). In addition, the standard requires that only the usual standard exceptions may occur, such as bad_alloc for a lack of memory or exceptions of user-defined operations. If functions called by a vector (functions for the element type or functions that are user-supplied) throw exceptions, the C++ standard library provides the following guarantees: 7.3 Vectors 279 1. If an element gets inserted with push_back() and an exception occurs, this function has no effect. 2. insert (), emplace(), emplace_back(), and push_back() either succeed or have no effect, provided that the copy/move operations (constructors and assignment operators) of the elements do not throw. 3. pop_back() does not throw any exceptions. 4. erase() does not throw if the copy/move operations (constructors and assignment operators) of the elements do not throw. 5. swap() and clear() do not throw. 6. If elements are used that never throw exceptions on copy/move operations (constructors and assignment operators), every operation is either successful or has no effect, Such elements might be “plain old data” (POD). POD describes types that use no special C++ feature. For example, every ordinary C structure is POD. All these guarantees are based on the requirements that destructors don’t throw. See Section 6.12.2, page 248, for a general discussion of exception handling in the STL. 7.3.5 Examples of Using Vectors The following example shows a simple use of vectors: // cont/vector1.cpp #include #include #include #include #include using namespace std; int main() { 1/ create empty vector for strings vector sentence; M/ reserve memory for five elements to avoid reallocation sentence. reserve(5) ; W/ append some elements sentence. push_back("Hello,"); sentence. insert (sentence. end() ,{"how", "are", "you" ,"?"}); UW print elements separated with spaces copy (sentence.cbegin(), sentence.cend(), ostream_iterator(cout," ")); 280 Chapter 7: STL Containers cout << endl; print “technical data"” cout <<" max_size( " << sentence.max_size() << endl; cout <<" size(): " << sentence.size() << endl; cout <<" capacity(): " << sentence.capacity() << endl; 1 swap second and fourth element swap (sentence(1], sentence[3]); insert element "always" before element "?: sentence. insert (find(sentence.begin() ,sentence.end() ,"?"), always") ; Hassign "\" to the last element sentence.back() = "!"; 1/ print elements separated with spaces copy (sentence.cbegin(), sentence.cend(), ostream_iterator(cout," ")); cout << endl; 1 print some “‘technical data’’ again cout <<" size(): " << sentence.size() << endl; " << sentence.capacity() << endl; cout <<" capacity( MW delete last two elements sentence. pop_back() ; sentence. pop_back(); // shrink capacity (since C++11) sentence.shrink_to_fit(); 1 print some “technical data’’ again cout <<" size(): " << sentence.size() << endl; cout <<" capacity(): " << sentence.capacity() << endl; ‘The output of the program might look like this: Hello, how are you ? max_size(): 1073741823 size(): 5 capacity(): 5 7.3 Vectors 281 Hello, you are how always ! size(): 6 capacity(): 10 size(): 4 capacity(): 4 Note my use of the word might. The values of max_size() and capacity () are unspecified and might vary from platform to platform. Here, for example, you can sce that the implementation seems to double the capacity if the capacity no longer fits and not necessarily shrinks if there is a request to do so. 7.3.6 Class vector For Boolean elements, the C++ standard library provides a specialization of vector<>. The goal is to have a version that is optimized to use less size than a usual implementation of vector<> for type bool. Such a usual implementation would reserve at least 1 byte for each element, The vector specialization usually uses internally only 1 bit for an element, so it is typically ight times smaller, But such an optimization also has a snag: In C++, the smallest addressable value must have a size of at least 1 byte. Thus, such a specialization of a vector needs special handling for references and iterators. As a result, a vector does not meet all requirements of other vectors. For example, a vector: :reference is not a true Ivalue and vector: : iterator is not a random- access iterator. Therefore, template code might work for vectors of any type except bool. In ad- dition, vector might perform worse than normal implementations, because element opera- tions have (o be transformed into bit operations. However, how vector is implemented is implementation specific. Thus, the performance (speed and memory) might differ Note that class vector is more than a specialization of vector<> for bool. It also provides some special bit operations. You can handle bits or flags in a more convenient way. vector has a dynamic size, so you can consider it a bitfield with dynamic size. Thus, you can add and remove bits. If you need a bitfield with static size, you should use bitset rather than a vector. Class bitset is covered in Section 12.5, page 650. The additional operations of vector are shown in Table 7.1 Operation Effect 2.#1ipO Negales all Boolean elements (complement of all bits) elidx] .£11p0 Negates the Boolean element with index idx (complement of a single bit) clidx] = val Assigns val to the Boolean clement with index idx (assignment to a single bit) clidx!] = clidx2] | Assigns the value of the element with index idx2 to the element with index idx Table 7.15. Special Operations of vector

You might also like