C++ Primer, Part II : The C++ Library
C++ Primer, Fifth Edition
Stanley B. LippmanJosée Lajoie
Barbara E. Moo
Chapter 9. Sequential Containers
A container holds a collection of objects of a specified type.
The sequential containers let the programmer control the order in which the elements are stored and accessed.
9.1 Overview of the Sequential Containers
There are a few rules of thumb that apply to selecting which container to use:
- Unless you have a reason to use another container, use a vector.
- If your program has lots of small elements and space overhead matters, don’t use list or forward_list.
- If the program requires random access to elements, use a vector or a deque.
- If the program needs to insert or delete elements in the middle of the container, use a list or forward_list.
- If the program needs to insert or delete elements at the front and the back, but not in the middle, use a deque.
- If the program needs to insert elements in the middle of the container only while reading input, and subsequently needs random access to the elements:
- First, decide whether you actually need to add elements in the middle of a container. It is often easier to append to a vector and then call the library sort function to reorder the container when you’re done with input.
- If you must insert into the middle, consider using a list for the input phase. Once the input is complete, copy the list into a vector.
9.2 Container Library Overview
- Some operations are provided by all container types.
- Some operations are specific to the sequential containers
The containers are class templates, we must supply additional information to generate a particular container type.
list<Sales_data> // list that holds Sales_data objects deque<double> // deque that holds doubles
9.2.1. Iterators
An iterator range is denoted by a pair of iterators : [ begin, end).
- If begin equals end, the range is empty
- If begin is not equal to end, there is at least one element in the range, and begin refers to the first element in that range
- We can increment begin some number of times until begin == end
while (begin != end) { *begin = val; // ok: range isn't empty so begin denotes an element ++begin; // advance the iterator to get the next element }
9.2.2. Container Type Members
9.2.3. begin and end Members
The begin and end operations yield iterators that refer to the first and one past the last element in the container.
There are several versions of begin and end:
lista = {"Milton", "Shakespeare", "Austen"}; auto it1 = a.begin(); // list<string>::iterator auto it2 = a.rbegin(); // list<string>::reverse_iterator auto it3 = a.cbegin(); // list<string>::const_iterator auto it4 = a.crbegin();// list<string>::const_reverse_iterator
9.2.4. Defining and Initializing a Container
9.2.5. Assignment and swap
The assignment operator replaces the entire range of elements in the left-hand container with copies of the elements from the right-hand operand
The swap operation exchanges the contents of two containers of the same type.
vector<string> svec1(10); // vector with 10 elements vector<string> svec2(24); // vector with 24 elements swap(svec1, svec2); // svec1 contains 24 string elements and svec2 contains 10
9.2.6. Container Size Operations
The container types have three size-related operations:
- The size member returns the number of elements in the container
- empty returns a bool that is true if size is zero and false otherwise
- max_size returns a number that is greater than or equal to the number of elements a container of that type can contain
9.2.7. Relational Operators
Comparing two containers performs a pairwise comparison of the elements.
vectorv1 = { 1, 3, 5, 7, 9, 12 }; vector v2 = { 1, 3, 9 }; vector v3 = { 1, 3, 5, 7 }; vector v4 = { 1, 3, 5, 7, 9, 12 }; v1 < v2 //true; v1 and v2 differ at element [2]: v1[2] is less than v2[2] v1 < v3 //false; all elements are equal, but v3 has fewer of them; v1 == v4 //true; each element is equal and v1 and v4 have the same size() v1 == v2 //false; v2 has fewer elements than v1
9.3 Sequential Container Operations
9.4 How a vector Grows
9.5 Additional string Operations
9.6 Container Adaptors
Chapter 10. Generic Algorithms
10.1 Overview
10.2 A First Look at the Algorithms
10.3 Customizing Operations
10.4 Revisiting Iterators
10.5 Structure of Generic Algorithms
10.6 Container-Specific Algorithms
Chapter 11. Associative Containers
Section 11.1 Using an Associative Container
11.2 Overview of the Associative Containers
11.3 Operations on Associative C
Chapter 12. Dynamic Memory
C++ automatic and static objects have well-defined lifetimes,
- Global objects are allocated at program start-up and destroyed when the program ends.
- Local, automatic objects are created and destroyed when the block in which they are defined is entered and exited.
- Local static objects are allocated before their first use and are destroyed when the program ends.
12.1 Dynamic Memory and Smart Pointers
In C++, dynamic memory is managed through a pair of operators:- new allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object
- delete takes a pointer to a dynamic object, destroys that object, and frees the associated memory.
- shared_ptr allows multiple pointers to refer to the same object
- unique_ptr “owns” the object to which it points
12.1.1. The shared_ptr Class
The smart pointers are templates . Therefore, when we create a smart pointer, we must supply the type to which the pointer can point.shared_ptr<string> p1; // shared_ptr that can point at a string shared_ptr< list< int> > p2; // shared_ptr that can point at a list of intsThe safest way to allocate and use dynamic memory is to call a library function named make_shared<T>().
make_shared uses its arguments to construct an object of the given type.
// shared_ptr that points to an int with value 42 shared_ptr<int> p3 = make_shared<int>(42); // p4 points to a string with value 9999999999 shared_ptr<string> p4 = make_shared<string>(10, '9'); // p5 points to an int that is value initialized to 0 shared_ptr<int> p5 = make_shared<int>(); // p6 points to a dynamically allocated, empty vector<string> auto p6 = make_shared<vector<string>>();When we copy or assign a shared_ptr, each shared_ptr keeps track of how many other shared_ptrs point to the same object We can think of a shared_ptr as if it has an associated counter, usually referred to as a reference count. Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages
auto r = make_shared<int>(42); // int to which r points has one user r = q; // assign to r, making it point to a different address // increase the use count for the object to which q points // reduce the use count of the object to which r had pointed // the object r had pointed to has no users; that object is automatically freed
Automatically Free the Associated Memory
- outside the function
// factory returns a shared_ptr pointing to a dynamically allocated object shared_ptr<Foo> factory(T arg) { // process arg as appropriate // shared_ptr will take care of deleting this memory return make_shared<Foo>(arg); }the object allocated by factory will be freed when appropriate.
void use_factory(T arg) { shared_ptr<Foo> p = factory(arg); // use p } // p goes out of scope; the memory to which p points is automatically freedBecause p is local to use_factory, it is destroyed when use_factory ends.
Because p is about to go away, the object to which p points will be destroyed and the memory in which that object resides will be freed. If you put shared_ptrs in a container, and you subsequently need to use some, but not all, of the elements, remember to erase the elements you no longer need. Using new to Dynamically Allocate and Initialize Objects,
int *pi = new int; // pi points to a dynamically allocated, // unnamed, uninitialized int int *pi = new int(1024); // object to which pi points has value 1024When we provide an initializer inside parentheses, we can use auto to deduce the type of the object we want to allocate from that initializer.
auto p1 = new auto(obj); // p points to an object of the type of obj // that object is initialized from obj // allocate and initialize a const int const int *pci = new const int(1024);By default, if new is unable to allocate the requested storage, it throws an exception of type bad_alloc
// if allocation fails, new returns a null pointer int *p1 = new int; // if allocation fails, new throws std::bad_alloc int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointerA delete expression performs two actions:
- destroys the object to which its given pointer points
- frees the corresponding memory
delete p; // p must point to a dynamically allocated object or be nullDeleting a pointer to memory that was not allocated by new, or deleting the same pointer value more than once, is undefined. Functions that return pointers (rather than smart pointers) to dynamic memory put a burden on their callers—the caller must remember to delete the memory. When we delete a pointer, that pointer becomes invalid. Although the pointer is invalid, on many machines the pointer continues to hold the address of the (freed) dynamic memory. We can assign nullptr to the pointer after we use delete
留言