Overview. Part II: Containers and Algorithms. Overview. Overview

Similar documents
CSCI-1200 Data Structures Fall 2017 Lecture 9 Iterators & STL Lists

CSCI-1200 Data Structures Fall 2017 Lecture 10 Vector Iterators & Linked Lists

CSCI-1200 Data Structures Fall 2013 Lecture 9 Iterators & Lists

STL: C++ Standard Library

CSCI-1200 Data Structures Spring 2018 Lecture 10 Vector Iterators & Linked Lists

Type Aliases. Examples: using newtype = existingtype; // C++11 typedef existingtype newtype; // equivalent, still works

CSCI-1200 Data Structures Fall 2014 Lecture 8 Iterators

Chapter 13: Copy Control. Overview. Overview. Overview

CSCI-1200 Data Structures Fall 2010 Lecture 8 Iterators

Abstract Container Types

CSCI-1200 Data Structures Spring 2016 Lecture 7 Iterators, STL Lists & Order Notation

Container Notes. Di erent Kinds of Containers. Types Defined by Containers. C++11 Container Notes C++11

Programming with Haiku

Containers in C++ and Java

Arrays and Pointers. Overview. Arrays Introducing Pointers C-Style Character Strings Multidimensioned Arrays

Sets and MultiSets. Contents. Steven J. Zeil. July 19, Overview of Sets and Maps 4

Short Notes of CS201

MODULE 33 --THE STL-- ALGORITHM PART I

CS201 - Introduction to Programming Glossary By

Chapter 5. The Standard Template Library.

STL components. STL: C++ Standard Library Standard Template Library (STL) Main Ideas. Components. Encapsulates complex data structures and algorithms

Exercise 6.2 A generic container class

vector<int> second (4,100); // four ints with value 100 vector<int> third (second.begin(),second.end()); // iterating through second

C++ 11 and the Standard Library: Containers, Iterators, Algorithms

Heap Management. Heap Allocation

calling a function - function-name(argument list); y = square ( z ); include parentheses even if parameter list is empty!

Purpose of Review. Review some basic C++ Familiarize us with Weiss s style Introduce specific constructs useful for implementing data structures

Introduction. Programming in C++ Pointers and arrays. Pointers in C and C++ Session 5 - Pointers and Arrays Iterators

C++ Programming Chapter 7 Pointers

Exceptions, Templates, and the STL

Concurrent Data Structures in C++ CSInParallel Project

18. Dynamic Data Structures I. Dynamic Memory, Addresses and Pointers, Const-Pointer Arrays, Array-based Vectors

Vector. Vector Class. class Vector { public: typedef unsigned int size_type;

This chapter serves mainly to gather and organize information about iterators. Some new concepts are also introduced for completeness.

Pointers and Arrays CS 201. This slide set covers pointers and arrays in C++. You should read Chapter 8 from your Deitel & Deitel book.

List, Stack, and Queues

Outline. Variables Automatic type inference. Generic programming. Generic programming. Templates Template compilation

Standard Library Reference

Assignment 1: grid. Due November 20, 11:59 PM Introduction

Today. andyoucanalsoconsultchapters6amd7inthetextbook. cis15-fall2007-parsons-lectvii.1 2

Computer Science II CSci 1200 Lecture 8 Iterators; Programming Examples

THE STANDARD TEMPLATE LIBRARY (STL) Week 6 BITE 1513 Computer Game Programming

Advanced Programming in C++ Container Design I

Lecture 10. Command line arguments Character handling library void* String manipulation (copying, searching, etc.)

CS11 Advanced C++ Spring 2018 Lecture 2

Lecture 10. To use try, throw, and catch Constructors and destructors Standard exception hierarchy new failures

CSCI-1200 Computer Science II Spring 2006 Test 3 Practice Problem Solutions

CS201 Some Important Definitions

use static size for this buffer

Patterns: Working with Arrays

Unit 1: Preliminaries Part 4: Introduction to the Standard Template Library

COEN244: Class & function templates

Data Structures and Algorithms

by Pearson Education, Inc. All Rights Reserved.

Templates and Vectors

Advanced C++ STL. Tony Wong

PIC 10A. Lecture 23: Intro to STL containers

by Pearson Education, Inc. All Rights Reserved. 2

Deallocation Mechanisms. User-controlled Deallocation. Automatic Garbage Collection

15. Pointers, Algorithms, Iterators and Containers II

Dynamic Data Structures

CS11 Advanced C++ Fall Lecture 1

+ Abstract Data Types

std::string Quick Reference Card Last Revised: August 18, 2013 Copyright 2013 by Peter Chapin

CS107 Handout 08 Spring 2007 April 9, 2007 The Ins and Outs of C Arrays

Template based set of collection classes STL collection types (container types)

what are strings today: strings strings: output strings: declaring and initializing what are strings and why to use them reading: textbook chapter 8

Think Like a Programmer

Linked Lists. Linked list: a collection of items (nodes) containing two components: Data Address (link) of the next node in the list

STL Quick Reference for CS 241

Linked Lists. Linked list: a collection of items (nodes) containing two components: Data Address (link) of the next node in the list

14. Pointers, Algorithms, Iterators and Containers II

1. The term STL stands for?

Function Templates. Consider the following function:

Lesson 13 - Vectors Dynamic Data Storage

Programming in C++ using STL. Rex Jaeschke

SSE2034: System Software Experiment 3

7.1 Optional Parameters

Copy Control 2008/04/08. Programming Research Laboratory Seoul National University

Introduction to C++ Systems Programming

Lecture 8. Xiaoguang Wang. February 13th, 2014 STAT 598W. (STAT 598W) Lecture 8 1 / 47

by Pearson Education, Inc. All Rights Reserved. 2

Arrays - Vectors. Arrays: ordered sequence of values of the same type. Structures: named components of various types

MID TERM MEGA FILE SOLVED BY VU HELPER Which one of the following statement is NOT correct.

Polymorphism. Programming in C++ A problem of reuse. Swapping arguments. Session 4 - Genericity, Containers. Code that works for many types.

UNIT- 3 Introduction to C++

Dodatak D Standardna string klasa

Sequential Containers. Ali Malik

Arrays and Linked Lists

CS197c: Programming in C++

Data Structures Lecture 3 Order Notation and Recursion

C++ Programming Lecture 2 Software Engineering Group

Definition: A data structure is a way of organizing data in a computer so that it can be used efficiently.

Writing Generic Functions. Lecture 20 Hartmut Kaiser hkaiser/fall_2013/csc1254.html

12. Pointers Address-of operator (&)

Chapter Six: Arrays and Vectors

Computer Science II Lecture 2 Strings, Vectors and Recursion

CS 103 Unit 11. Linked Lists. Mark Redekopp

CMSC 341 Lecture 6 Templates, Stacks & Queues. Based on slides by Shawn Lupoli & Katherine Gibson at UMBC

Trees can be used to store entire records from a database, serving as an in-memory representation of the collection of records in a file.

Transcription:

Part II: Containers and Algorithms Overview Sequential Containers Associative Containers Generic Algorithms C++ is about efficient programming with abstractions. The Standard Library is a good example: The library defines a number of container classes and a family of generic algorithms that let us write programs that are succinct, abstract, and efficient The library worries about bookkeeping details in particular, taking care of memory management so that our programs can worry about the actual problems we need to solve. 1 2 Overview We introduced the vector container type. We'll learn more in Chapter 9 about vector and the other sequential container types provided by the library We'll also cover more operations provided by the string type We can think of a string as a special kind of container that contains only characters The string type supports many, but not all, of the container operations. Overview The library also defines several associative containers Elements in an associative container are ordered by key rather than sequentially The associative containers share many operations with the sequential containers and also define operations that are specific to the associative containers 3 4 1

Overview Overview Chapter 11 introduces the generic algorithms The algorithms typically operate on a range of elements from a container or other sequence The algorithms library offers efficient implementations of various classical algorithms, such as searching, sorting, and other common tasks copy algorithm copies elements from one sequence to another find looks for a given element; and so on. The algorithms are generic in two ways: they can be applied to different kinds of containers those containers may contain elements of most types. The library is designed so that the container types provide a common interface: If two containers offer a similar operation, then that operation will be defined identically for both containers For example, all the containers have an operation to return the number of elements in the container. All the containers name that operation size, and they all define a type named size_type that is the type of the value returned by size Similarly, the algorithms have a consistent interface. For example, most algorithms operate on a range of elements specified by a pair of iterators. 5 6 Overview Because the container operations and algorithms are defined consistently, learning the library becomes easier Once you understand how an operation works, you can apply that same operation to other containers More importantly, this commonality of interface leads to more flexible programs It is often possible to take a program written to use one container type and change it to use a different container without having to rewrite code As we'll see, the containers offer different performance tradeoffs, and the ability to change container types can be valuable when fine-tuning the performance of a system. 7 Chapter 9: IO Library Defining a Sequential Container Iterators and Iterator Ranges Sequence Container Operations How a vector Grows Deciding Which Container to Use strings Revisited Container Adaptors 8 2

Overview Elements in a sequential container are stored and accessed by position. The library also defines several associative containers, which hold elements whose order depends on a key (next chapter) The container classes share a common interface This fact makes the library easier to learn; what we learn about one type applies to another Each container type offers a different set of time and functionality tradeoffs. Often a program using one type can be fine-tuned by substituting another container without changing our code beyond the need to change type declarations. 9 Overview A container holds a collection of objects of a specified type We've used one kind of container already: the library vector type. It is a sequential container. It holds a collection of elements of a single type, making it a container Those elements are stored and accessed by position, making it a sequential container The order of elements in a sequential container is independent of the value of the elements Instead, the order is determined by the order in which elements are added to the container. 10 Overview The library defines three kinds of sequential containers: vector list deque ("double-ended queue, pronounced "deck") These types differ in how elements are accessed and the relative run-time cost of adding or removing elements The library also provides three container adaptors Effectively, an adaptor adapts an underlying container type by defining a new interface in terms of the operations provided by the original type The sequential container adaptors are stack, queue, and priority_queue. 11 Overview Containers define only a small number of operations Many additional operations are provided by the algorithms library For those operations that are defined by the containers, the library imposes a common interface The containers vary as to which operations they provide, but if two containers provide the same operation, then the interface (name and number of arguments) will be the same for both container types 12 3

Overview Sequential Container Types The set of operations on the container types form a kind of hierarchy: Some operations are supported by all container types. Other operations are common to only the sequential or only the associative containers. Still others are common to only a subset of either the sequential or associative containers. vector supports fast random access list supports fast insertion/deletion Deque double-ended queue 13 14 Sequential Container Adaptors stack last in/first out stack queue first in/first out queue priority_queue priority-managed queue Defining a Sequential Container 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 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 15 16 4

Initializing Container Elements Each container defines a default constructor that creates an empty container of the specified type Recall that a default constructor takes no arguments In 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 Container When 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 17 18 Intializing a Container as a Copy of Another Container 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 Elements Although we cannot copy the elements from one kind of container to another directly, we can do so indirectly by passing a pair of iterators 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 19 20 5

Initializing as a Copy of a Range of Elements 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: Initializing as a Copy of a Range of Elements // 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()); 21 22 Initializing as a Copy of a Range of Elements 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 *); Allocating and Initializing a Specified Number of Elements When 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? // use entire array to initialize words2 list<string> words2(words, words + words_size); 23 24 6

Allocating and Initializing a Specified Number of Elements 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); Allocating and Initializing a Specified Number of Elements When we do not supply an element initializer, the library generates a value-initialized 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. vector<string> svec(get_word_count("chimera")); 25 26 Constraints on Types that a Container Can Hold While most types can be used as the element type of a container, there are two constraints that element types must meet: The element type must support assignment. We must be able to copy objects of the element type 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. Constraints on Types that a Container Can Hold Explain the following initializations. Indicate if any are in error, and if so, why. int ia[7] = { 0, 1, 1, 2, 3, 5, 8 }; string sa[6] = { "Fort Sumter", "Manassas", "Perryville", "Vicksburg", "Meridian", "Chancellorsville" }; (a) vector<string> svec(sa, sa+6); (b) list<int> ilist( ia+4, ia+6); (c) vector<int> ivec(ia, ia+8); (d) list<string> slist(sa+6, sa); 27 28 7

Container Operations May Impose Additional Requirements The 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 Container Operations May Impose Additional Requirements 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 29 30 Container Operations May Impose Additional Requirements 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 vector<foo> empty; // ok: no need for default constructor vector<foo> bad(10); // error: no default constructor for Foo vector<foo> ok(10, 1); // ok: each element initialized to 1 Containers of Containers Because 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: vector< vector<string> > lines; // vector of vectors Note the spacing used when specifying a container element type as a container: vector< vector<string>> lines; // error: >> treated as shift operator 31 32 8

Iterators and Iterator Ranges Iterators and Iterator Ranges The constructors that take a pair of iterators are an example of a common form used extensively throughout the library Each of the container types has several companion iterator types Like the containers, the iterators have a common interface If an iterator provides an operation, then the operation is supported in the same way for each iterator that supplies that operation For example, all the container iterators let us read an element from a container, and they all do so by providing the dereference operator Similarly, they all provide increment and decrement operators to allow us to go from one element to the next 33 34 Common Iterator Operations *iter return a reference to the element referred to by the iterator iter iter->mem dereference iter and fetch the member named mem from the underlying element ++iter iter++ increment iter to refer to the next element in the container Common Iterator Operations --iter iter decrement iter to refer to the previous element in the container iter1 == iter2 iter1!= iter2 compare two iterators for equality (inequality). Two iterators are equal if they refer to the same element of the same container or if they are the off-the-end iterator for the same container 35 36 9

Iterators on vector and deque Support Additional Operations iter + n, iter - n Adding (subtracting) an integral value n to (from) an iterator yields an iterator that many elements forward (backward) within the container. The resulting iterator must refer to an element in the container or one past the end of the container Iterators on vector and deque Support Additional Operations iter1 += iter2, iter1 -= iter2 Compound-assignment versions of iterator addition and subtraction. Assigns the value of adding or subtracting iter1 and iter2 into iter1 37 38 Iterators on vector and deque Support Additional Operations iter1 - iter2 Subtracting two iterators yields the number that when added to the right-hand iterator yields the lefthand iterator. The iterators must refer to elements in the same container or one past the end of the container Iterators on vector and deque Support Additional Operations Supported only for vector and deque >, >=, <, <= Relational operators on iterators. One iterator is less than another if it refers to an element whose position in the container is ahead of the one referred to by the other iterator. The iterators must refer to elements in the same container or one past the end of the container 39 40 10

Iterators on vector and deque Support Additional Operations The reason that only vector and deque support the relational operators is that only vector and deque offer fast, random access to their element These containers are guaranteed to let us efficiently jump directly to an element given its position in the container Because these containers support random access by position, it is possible for their iterators to efficiently implement the arithmetic and relational operations Iterators on vector and deque Support Additional Operations For example, we could calculate the midpoint of a vector as follows: vector<int>::iterator iter = vec.begin() + vec.size()/2; // copy elements from vec into ilist list<int> ilist(vec.begin(), vec.end()); ilist.begin() + ilist.size()/2; // error: no addition on list iterators 41 42 Iterator Ranges Iterator Ranges The concept of an iterator range is fundamental to the standard library An iterator range is denoted by a pair of iterators that refer to two elements, or to one past the last element, in the same container These two iterators, often referred to as first and last, or beg and end, mark a range of elements from the container. Although the names last and end are common, they are a bit misleading The second iterator never refers to the last element of the range. Instead, it refers to a point one past the last element This element range is called a left-inclusive interval. The standard notation for such a range is // to be read as: includes first and each element up to but not // including last [ first, last ) 43 44 11

Requirements on Iterators Forming an Iterator Range Two iterators, first and last, form an iterator range, if They refer to elements of, or one-past-the-end of, the same container. If the iterators are not equal, then it must be possible to reach last by repeatedly incrementing first. In other words, last must not precede first in the container The compiler cannot itself enforce these requirements. It does not know to which container an iterator is bound, nor does it know how many elements are in a container. Failing to meet these requirements results in undefined run-time behavior. Programming Implications of Using Left- Inclusive Ranges The library uses left-inclusive ranges because such ranges have two convenient properties Assuming first and last denote a valid iterator range, then When first equals last, the range is empty. When first is not equal to last, there is at least one element in the range, and first refers to the first element in that range. Moreover, we can advance first by incrementing it some number of times until first == last. 45 46 Some Container Operations Invalidate Iterators some container operations change the internal state of a container or cause the elements in the container to be moved Such operations invalidate all iterators that refer to the elements that are moved and may invalidate other iterators as well Using an invalidated iterator is undefined, and likely to lead to the same kinds of problems as using a dangling pointer. Some Container Operations Invalidate Iterators each container defines one or more erase functions. These functions remove elements from the container. Any iterator that refers to an element that is removed has an invalid value the iterator was positioned on an element that no longer exists within the container When writing programs that use iterators, we must be aware of which operations can invalidate the iterators. It is a serious run-time error to use an iterator that has been invalidated 47 48 12

Sequence Container Operations Container Typedefs Each sequential container defines a set of useful typedefs and supports operations that let us Add elements to the container Delete elements from the container Determine the size of the container Fetch the first and last elements from the container, if any size_type Unsigned integral type large enough to hold size of largest possible container of this container type iterator Type of the iterator for this container type const_iterator type of the iterator that can read but not write the elements reverse_iterator Iterator that addresses elements in reverse order 49 50 Container Typedefs Container Typedefs difference_type Signed integral type large enough to hold the difference, which might be negative, between two iterators value_type Element type reference Element's lvalue type; synonym for value_type& const_reference element's const lvalue type; same as const value_type& a reverse iterator is an iterator that goes backward through a container and inverts the iterator operations For example, saying ++ on a reverse iterator yields the previous element in the container The last three types in Table 9.5 on the facing page let us use the type of the elements stored in a container without directly knowing what that type is If we need the element type, we refer to the container's value_type If we need a reference to that type, we use reference or const_reference. 51 52 13

Container Typedefs begin and end Members // iter is the iterator type defined by list<string> list<string>::iterator iter; // cnt is the difference_type type defined by vector<int> vector<int>::difference_type cnt; The begin and end operations yield iterators that refer to the first and one past the last element in the container These iterators are most often used to form an iterator range that encompasses all the elements in the container c.begin() c.end() c.rbegin() c.rend() 53 54 Adding Elements to a Sequential Container Every sequential container supports push_back, which appends an element to the back of the container // read from standard input putting each word onto the end string text_word; while (cin >> text_word) container.push_back(text_word); Adding Elements to a Sequential Container In addition to push_back, the list and deque containers support an analogous operation named push_front. This operation inserts a new element at the front of the container The call to push_back creates a new element at the end of container, increasing the size of container by one 55 56 14

Adding Elements to a Sequential Container list<int> ilist; // add elements at the end of ilist for (size_t ix = 0; ix!= 4; ++ix) ilist.push_back(ix); Alternatively, we could use push_front // add elements to the start of ilist for (size_t ix = 0; ix!= 4; ++ix) ilist.push_front(ix); After executing both loops, ilist holds the sequence 3,2,1,0,0,1,2,3. 57 Operations that Add Elements to a Sequential Container Valid only for list or deque c.insert(p,t) Inserts element with value t before the element referred to by iterator p. Returns an iterator referring to the element that was added c.insert(p,n,t) Inserts n elements with value t before the element referred to by iterator p. Returns void c.insert(p,b,e) Inserts elements in the range denoted by iterators b and e before the element referred to by iterator p. Returns void. 58 Adding Elements at a Specified Point in the Container The push_back and push_front operations provide convenient ways to insert a single element at the end or beginning of a sequential container More generally, insert allows us to insert elements at any particular point in the container There are three versions of insert The first takes an iterator and an element value. The iterator refers to the position at which to insert the value. We could use this version of insert to insert an element at the beginning of a container Adding Elements at a Specified Point in the Container vector<string> svec; list<string> slist; string spouse("beth"); // equivalent to calling slist.push_front (spouse); slist.insert(slist.begin(), spouse); // no push_front on vector but we can insert before begin() svec.insert(svec.begin(), spouse); 59 60 15

Adding Elements at a Specified Point in the Container insert returns an iterator referring to the newly inserted element. We could use the return value to repeatedly insert elements at a specified position in the container list<string> lst; list<string>::iterator iter = lst.begin(); while (cin >> word) iter = lst.insert(iter, word); // same as calling push_front Inserting a Range of Elements A second form of insert adds a specified number of identical elements at an indicated position svec.insert(svec.end(), 10, "Anna"); This code inserts ten elements at the end of svec and initializes each of those elements to the string "Anna". 61 62 Inserting a Range of Elements The final form of insert adds a range of elements denoted by an iterator pair into the container For example, given the following array of strings string sarray[4] = {"quasi", "simba", "frollo", "scar"}; we can insert all or a subset of the array elements into our list of strings: // insert all the elements in sarray at end of slist slist.insert(slist.end(), sarray, sarray+4); list<string>::iterator slist_iter = slist.begin(); // insert last two elements of sarray before slist_iter slist.insert(slist_iter, sarray+2, sarray+4); 63 Inserting Elements Can Invalidate Iterators Adding elements to a vector can cause the entire container to be relocated If the container is relocated, then all iterators into the container are invalidated Even if the vector does not have to be relocated, any iterator to an element after the one inserted is invalidated. Iterators may be invalidated after doing any insert or push operation on a vector or deque. When writing loops that insert elements into a vector or a deque, the program must ensure that the iterator is refreshed on each trip through the loop. 64 16

Avoid Storing the Iterator Returned from end When we add elements to a vector or deque, some or all of the iterators may be invalidated It is safest to assume that all iterators are invalid This advice is particularly true for the iterator returned by end. That iterator is always invalidated by any insertion anywhere in the container Avoid Storing the Iterator Returned from end As an example, consider a loop that reads each element, processes it and adds a new element following the original We want the loop to process each original element We'll use the form of insert that takes a single value and returns an iterator to the element that was just inserted After each insertion, we'll increment the iterator that is returned so that the loop is positioned to operate on the next original element. 65 66 Avoid Storing the Iterator Returned from end vector<int>::iterator first = v.begin(), last = v.end(); // cache end iterator // diaster: behavior of this loop is undefined while (first!= last) { // do some processing // insert new value and reassign first, which otherwise // would be invalid first = v.insert(first, 42); ++first; // advance first just past the element we added } Adding an element invalidates the iterator stored in last. That iterator neither refers to an element in v nor any longer refers to Avoid Storing the Iterator Returned from end one past the last element in v 67 68 Rather than storing the end iterator, we must recompute it after each insertion: // safer: recalculate end on each trip whenever the loop // adds/erases elements while (first!= v.end()) { // do some processing first = v.insert(first, 42); // insert new value ++first; // advance first just past the element we added } 17

Relational Operators Relational Operators Relational operators can be used to compare two containers The containers must be the same kind of container and must hold elements of the same type We can compare a vector<int> only with another vector<int>. We cannot compare a vector<int> with a list<int> or a vector<double>. Comparing two containers is based on a pairwise comparison of the elements of the containers The comparison uses the same relational operator as defined by the element type Comparing two containers using!= uses the!= operator for the element type 69 70 Relational Operators Relational Operators These operators work similarly to the string relationals If both containers are the same size and all the elements are equal, then the two containers are equal; otherwise, they are unequal. If the containers have different sizes but every element of the shorter one is equal to the corresponding element of the longer one, then the shorter one is considered to be less than the other. If neither container is an initial subsequence of the other, then the comparison depends on comparing the first unequal elements Examples /* ivec1: 1 3 5 7 9 12 ivec2: 0 2 4 6 8 10 12 ivec3: 1 3 9 ivec4: 1 3 5 7 ivec5: 1 3 5 7 9 12 */ // ivec1 and ivec2 differ at element[0]: ivec1 greater than ivec2 ivec1 < ivec2 // false ivec2 < ivec1 // true 71 72 18

Relational Operators Relational Operators Examples /* ivec1: 1 3 5 7 9 12 ivec2: 0 2 4 6 8 10 12 ivec3: 1 3 9 ivec4: 1 3 5 7 ivec5: 1 3 5 7 9 12 */ // ivec1 and ivec3 differ at element[2]: ivec1 less than ivec3 ivec1 < ivec3 // true Examples /* ivec1: 1 3 5 7 9 12 ivec2: 0 2 4 6 8 10 12 ivec3: 1 3 9 ivec4: 1 3 5 7 ivec5: 1 3 5 7 9 12 */ // all elements equal, but ivec4 has fewer elements, so ivec1 is greater than ivec4 ivec1 < ivec4 // false 73 74 Relational Operators Relational Operators Examples /* ivec1: 1 3 5 7 9 12 ivec2: 0 2 4 6 8 10 12 ivec3: 1 3 9 ivec4: 1 3 5 7 ivec5: 1 3 5 7 9 12 */ ivec1 == ivec5 // true; each element equal and same number of //elements ivec1 == ivec4 // false; ivec4 has fewer elements than ivec1 ivec1!= ivec4 // true; ivec4 has fewer elements than ivec1 We can compare two containers only if the same relational operator defined for the element types Each container relational operator executes by comparing pairs of elements from the two containers: ivec1 < ivec2 Assuming ivec1 and ivec2 are vector<int>, then this comparison uses the built-in int less-than operator If the vectors held strings, then the string less-than operator would be used. 75 76 19

Relational Operators Container Size Operations If the vectors held objects of the Sales_item type that we used, then the comparison would be illegal We did not define the relational operators for Sales_item If we have two containers of Sales_items, we could not compare them Each container type supports four size-related operations size() returns the number of elements in the container; empty() returns a bool that is true if size is zero and false otherwise vector<sales_item> storea; vector<sales_item> storeb; if (storea < storeb) // error: Sales_item has no less-than operator 77 78 Container Size Operations Container Size Operations The resize operation changes the number of elements in the container If the current size is greater than the new size, then elements are deleted from the back of the container. If the current size is less than the new size, then elements are added to the back of the container list<int> ilist(10, 42); // 10 ints: each has value 42 ilist.resize(15); // adds 5 elements of value 0 to back of ilist ilist.resize(25, -1); // adds 10 elements of value -1 to back ilist.resize(5); // erases 20 elements from the back of ilist resize can invalidate iterators. A resize operation on a vector or deque potentially invalidates all iterators 79 80 20

Erasing Elements Removing the First or Last Element Recall that there is both a general insert operation that inserts anywhere in the container and specific push_front and push_back operations to add elements only at the front or back Similarly, there is a general erase and specific pop_front and pop_back operations to remove elements while (!ilist.empty()) { process(ilist.front()); // do something with the current top of ilist ilist.pop_front(); // done; remove first element } 81 82 Removing an Element From within the Container The more general way to remove an element, or range of elements, is through erase There are two versions of erase: delete a single element referred to by an iterator delete a range of elements marked by a pair of iterators. Both forms of erase return an iterator referring to the location after the element or range that was removed Removing an Element From within the Container The erase operation is often used after finding an element that should be removed from the container. The easiest way to find a given element is to use the library find algorithm find returns an iterator referring to the first element with that value or the off-the-end iterator string searchvalue("quasimodo"); list<string>::iterator iter = find(slist.begin(), slist.end(), searchvalue); 83 if (iter!= slist.end()) slist.erase(iter); 84 21

Removing All the Elements in a Container Removing All the Elements in a Container To delete all the elements in a container, we could either call clear or pass the iterators from begin and end to erase slist.clear(); // delete all the elements within the container slist.erase(slist.begin(), slist.end()); // equivalent The iterator-pair version of erase lets us delete a subrange of elements // delete range of elements between two values list<string>::iterator elem1, elem2; // elem1 refers to val1 elem1 = find(slist.begin(), slist.end(), val1); // elem2 refers to the first occurrence of val2 after val1 elem2 = find(elem1, slist.end(), val2); 85 // erase range from val1 up to but not including val2 slist.erase(elem1, elem2); 86 Assignment and swap The assignment-related operators act on the entire container Except for swap, they can be expressed in terms of erase and insert operations The assignment operator erases the entire range of elements in the left-hand container and then inserts the elements of the right-hand container object into the left-hand container Assignment and swap c1 = c2; // replace contents of c1 with a copy of elements in c2 // equivalent operation using erase and insert c1.erase(c1.begin(), c1.end()); // delete all elements in c1 c1.insert(c1.begin(), c2.begin(), c2.end()); // insert c2 After the assignment, the left- and right-hand containers are equal: Even if the containers had been of unequal size, after the assignment both containers have the size of the right-hand operand Assignment and the assign operations invalidate all iterators into the left-hand container. 87 88 22

Assignment and swap The assign operation deletes all the elements in the container and then inserts new elements as specified by the arguments Like the constructor that copies elements from a container, the assignment operator (=) can be used to assign one container to another only if the container and element type are the same If we want to assign elements of a different but compatible element type and/or from a different container type, then we must use the assign operation. For example, we could use assign to assign a range of char* values from a vector into a list of string. Assignment and swap c.assign(b,e) Replaces the elements in c by those in the range denoted by iterators b and e. The iterators b and e must not refer to elements in c. c.assign(n,t) Replaces the elements in c by n elements with value t. 89 90 Assignment and swap The arguments to assign determine how many elements are inserted and what values the new elements will have // equivalent to slist1 = slist2 slist1.assign(slist2.begin(), slist2.end()); A second version of assign takes an integral value and an element value. It replaces the elements in the container by the specified number of elements, each of which has the specified element value Assignment and swap c1.swap(c2) Swaps contents: After the call c1 has elements that were in c2, and c2 has elements that were in c1. c1 and c2 must be the same type. Execution time usually much faster than copying elements from c2 to c1. // equivalent to: slist1.clear(); // followed by slist1.insert(slist1.begin(), 10, "Hiya!"); slist1.assign(10, "Hiya!"); // 10 elements; each one is Hiya! 91 92 23

Using swap to Avoid the Cost of Deleting Elements The swap operation swaps the values of its two operands. The types of the containers must match: The operands must be the same kind of container, and they must hold values of the same type After the call to swap, the elements that had been in the right-hand operand are in the left, and vice versa vector<string> svec1(10); // vector with 10 elements vector<string> svec2(24); // vector with 24 elements svec1.swap(svec2); Using swap to Avoid the Cost of Deleting Elements swap does not invalidate iterators. After swap, iterators continue to refer to the same elements, although those elements are now in a different container For example, had iter referred to the string at position svec1[3] before the swap it will refer to the element at position svec2[3] after the swap. vector<string> svec1(10); // vector with 10 elements vector<string> svec2(24); // vector with 24 elements svec1.swap(svec2); 93 94 How a vector Grows When we insert or push an element onto a container object, the size of that object increases by one Similarly, if we resize a container to be larger than its current size, then additional elements must be added to the container The library takes care of allocating the memory to hold these new elements 95 How a vector Grows To support fast random access, vector elements are stored contiguously: each element is adjacent to the previous element If there is no room in the vector for the new element, it cannot just add an element somewhere else in memory because the elements must be contiguous for indexing to work Instead, the vector must allocate new memory to hold the existing elements plus the new one, copy the elements from the old location into the new space, add the new element, and deallocate the old memory If vector did this memory allocation and deallocation each time we added an element, then performance would be unacceptably slow. 96 24

How a vector Grows How a vector Grows There is no comparable allocation issue for containers that do not hold their elements contiguously For example, to add an element to a list, the library only needs to create the new element and chain it into the existing list. There is no need to reallocate or copy any of the existing elements. For most applications the best container to use is a vector. The reason is that library uses allocation strategies that minimize the costs of storing elements contiguously That cost is usually offset by the advantages in accessing elements that contiguous storage allows. 97 The way vectors achieve fast allocation is by allocating capacity beyond what is immediately needed Thus, there is no need to reallocate the container for each new element The exact amount of additional capacity allocated varies across different implementations of the library. This allocation strategy is dramatically more efficient than reallocating the container each time an element is added In fact, its performance is good enough that in practice a vector usually grows more efficiently than a list or a deque. 98 capacity and reserve Members capacity and reserve Members The details of how vector handles its memory allocation are part of its implementation However, a portion of this implementation is supported by the interface to vector The vector class includes two members, capacity and reserve, that let us interact with the memoryallocation part of vector's implementation The capacity operation tells us how many elements the container could hold before it must allocate more space The reserve operation lets us tell the vector how many elements it should be prepared to hold. 99 vector<int> ivec; // size should be zero; capacity is implementation defined cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; // give ivec 24 elements for (vector<int>::size_type ix = 0; ix!= 24; ++ix) ivec.push_back(ix); // size should be 24; capacity will be >= 24 and is // implementation defined cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; ivec: size: 0 capacity: 0 ivec: size: 24 capacity: 32 100 25

capacity and reserve Members capacity and reserve Members We could now reserve some additional space: ivec.reserve(50); // sets capacity to at least 50; might be more // size should be 24; capacity will be >= 50 and is implementation defined cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; We might next use up that reserved capacity as follows: // add elements to use up the excess capacity while (ivec.size()!= ivec.capacity()) ivec.push_back(0); // size should be 50; capacity should be unchanged cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; ivec: size: 24 capacity: 50 101 As long as there is excess capacity, the vector must not reallocate its elements. The output indicates that at this point we've used up the reserved capacity, and size and capacity are equal: ivec: size: 50 capacity: 50 102 capacity and reserve Members capacity and reserve Members If we now add another element, the vector will have to reallocate itself: ivec.push_back(42); // add one more element // size should be 51; capacity will be >= 51 and is // implementation defined cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; The output from this portion of the program ivec: size: 51 capacity: 100 Indicates that this vector implementation appears to follow a strategy of doubling the current capacity each time it has to allocate new storage 103 Each implementation of vector is free to choose its own allocation strategy However, it must provide the reserve and capacity functions, and it must not allocate new memory until it is forced to do so How much memory it allocates is up to the implementation Different libraries will implement different strategies. 104 26

Deciding Which Container to Use As we saw in the previous section, allocating memory to hold elements in contiguous storage has impacts on the memory allocation strategies and overhead of a container By using clever implementation techniques, library authors minimize this allocation overhead Whether elements are stored contiguously has other significant impacts on: The costs to add or delete elements from the middle of the container The costs to perform nonsequential access to elements of the container Deciding Which Container to Use The degree to which a program does these operations should be used to determine which type of container to use The vector and deque types provide fast non-sequential access to elements at the cost of making it expensive to add or remove elements anywhere other than the ends of the container The list type supports fast insertion and deletion anywhere but at the cost of making nonsequential access to elements expensive. 105 106 How Insertion Affects Choice of Container A list represents noncontiguous memory and allows for both forward and backward traversal one element at a time It is efficient to insert or erase an element at any point Inserting or removing an element in a list does not move any other elements. Random access, on the other hand, is not supported Accessing an element requires traversing the intervening elements. How Insertion Affects Choice of Container Inserting (or removing) anywhere except at the back of a vector requires that each element to the right of the inserted (or deleted) element be moved For example, if we have a vector with 50 elements and we wish to erase element number 23, then each of the elements after 23 have to be moved forward by one position Otherwise, there'd be a hole in the vector, and the vector elements would no longer be contiguous 107 108 27

How Insertion Affects Choice of Container A deque is a more complicated data structure. We are guaranteed that adding or removing elements from either end of the deque is a fast operation Adding or removing from the middle will be more expensive A deque offers some properties of both list and vector: Like vector, it is inefficient to insert or erase elements in the middle of the deque. Unlike vector, a deque offers efficient insert and erase at the front as well as at the back. Unlike list and like vector, a deque supports fast random access to any element. How Insertion Affects Choice of Container Inserting elements at the front or back of a deque does not invalidate any iterators Erasing the front or back element invalidates only iterators referring to the element(s) erased Inserting or erasing anywhere else in the deque invalidates all iterators referring to elements of the deque. 109 110 How Access to the Elements Affects Choice of Container Both vector and deque support efficient random access to their elements That is, we can efficiently access element 5, then 15, then 7, and so on. Random access in a vector can be efficient because each access is to a fixed offset from the beginning of the vector. It is much slower to jump around in a list. the only way to move between the elements of a list is to sequentially follow the pointers Moving from the 5th to the 15th element requires visiting every element between them 111 Hints on Selecting Which Container to Use There are a few rules of thumb that apply to selecting which container to use: 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. If the program needs to insert or delete elements at the front and the back, but not in the middle, of the container, use a deque. 112 28

Hints on Selecting Which Container to Use If we need to insert elements in the middle of the container only while reading input and then need random access to the elements, consider reading them into a list and then reordering the list as appropriate for subsequent access and copying the reordered list into a vector. Hints on Selecting Which Container to Use What if the program needs to randomly access and insert and delete elements in the middle of the container? This decision will depend on the relative cost of doing random access to list elements versus the cost of copying elements when inserting or deleting elements in a vector or deque In general, the predominant operation of the application (whether it does more access or more insertion or deletion) should determine the choice of container type. 113 114 strings Revisited strings Revisited We can create a string: as the empty string, by providing no argument; as a copy of another string; from a pair of iterators; or from a count and a character string s1; // s1 is the empty string string s2(5, 'a'); // s2 == "aaaaa" string s3(s2); // s3 is a copy of s2 string s4(s3.begin(), s3.begin() + s3.size() / 2); // s4 == "aa" 115 There is another constructor that takes a pointer to an element in a character array and a count of how many characters to copy. Because the constructor takes a count, the array does not have to be nullterminated char *cp = "Hiya"; // null-terminated array char c_array[] = "World!!!!"; // null-terminated char no_null[] = {'H', 'i'}; // not null-terminated string s1(cp); // s1 == "Hiya" string s2(c_array, 5); // s2 == "World" string s3(c_array + 5, 4); // s3 == "!!!!" string s4(no_null); // runtime error: not null-terminated string s5(no_null, 2); // ok: s5 == "Hi" 116 29

Other Ways to Change a string Many of the container operations that string supports operate in terms of iterators For example, erase takes an iterator or iterator range to specify which element(s) to remove from the container Similarly, the first argument to each version of insert takes an iterator to indicate the position before which to insert the values represented by the other arguments. Other Ways to Change a string s.insert(p, t) Insert copy of value t before element referred to by iterator p s.insert(p, n, t) Insert n copies of t before p. Returns void s.insert(p, b, e) Insert elements in range denoted by iterators b and e before p. Returns void. 117 118 Other Ways to Change a string s.assign(b, e) Replace s by elements in range denoted by b and e. For string, returns s, for the containers, returns void. s.assign(n, t) Replace s by n copies of value t. For string, returns s, for the containers, returns void. s.erase(p) Erase element referred to by iteartor p.returns an iterator to the element after the one deleted s.erase(b, e) Remove elements in range denoted by b and e. Returns an iterator to the first element after the range deleted. 119 Specifying the New Contents The characters to insert or assign into the string can be taken from a character array or another string For example, we can use a null-terminated character array as the value to insert or assign into a string: char *cp = "Stately plump Buck"; string s; s.assign(cp, 7); // s == "Stately" s.insert(s.size(), cp + 7); // s == "Stately plump Buck" 120 30

Specifying the New Contents Similarly, we can insert a copy of one string into another as follows s = "some string"; s2 = "some other string"; // 3 equivalent ways to insert all the characters from s2 // insert iterator range before s.begin() s.insert(s.begin(), s2.begin(), s2.end()); // insert copy of s2 before position 0 in s s.insert(0, s2); // insert s2.size() characters from s2 starting at s2[0] s.insert(0, s2, 0, s2.size()); string-only Operations The string type provides several other operations that the containers do not: The substr function that returns a substring of the current string The append and replace functions that modify the string A family of find functions that search the string 121 122 The substr Operation The substr operation lets us retrieve a substring from a given string We can pass substr a starting position and a count. It creates a new string that has that many characters, (up to the end of the string) from the target string, starting at the given position string s("hello world"); // return substring of 5 characters starting at position 6 string s2 = s.substr(6, 5); // s2 = world Alternatively, we could obtain the same result by writing: // return substring from position 6 to the end of s string s3 = s.substr(6); // s3 = world 123 The append and replace Functions There are six overloaded versions of append and ten versions of replace. The append and replace functions are overloaded using the same set of arguments These arguments specify the characters to add to the string. In the case of append, the characters are added at the end of the string In the replace function, these characters are inserted in place a specified range of existing characters in the string 124 31

The append and replace Functions The append operation is a shorthand way of inserting at the end string s("c++ Primer"); // initialize s to "C++ Primer" s.append(" 3rd Ed."); // s == "C++ Primer 3rd Ed." // equivalent to s.append(" 3rd Ed.") s.insert(s.size(), " 3rd Ed."); The append and replace Functions The replace operations remove an indicated range of characters and insert a new set of characters in their place. The replace operations have the same effect as calling erase and insert // starting at position 11, erase 3 characters and then insert "4th" s.replace(11, 3, "4th"); // s == "C++ Primer 4th Ed." // equivalent way to replace "3rd" by "4th" s.erase(11, 3); // s == "C++ Primer Ed." s.insert(11, "4th"); // s == "C++ Primer 4th Ed." 125 126 The append and replace Functions In the previous call to replace, the text we inserted happens to be the same size as the text we removed. We could insert a larger or smaller string s.replace(11, 3, "Fourth"); // s == "C++ Primer Fourth Ed." 127 string Search Operations The string class provides six search functions, each named as a variant of find The operations all return a string::size_type value that is the index of where the match occurred, or a special value named string::npos if there is no match There are four versions of each of the search operations, each of which takes a different set of arguments Basically, these operations differ as to whether they are looking for a single character, another string, a C- style, null-terminated string, or a given number of characters from a character array. 128 32