Character-set searches and resizable vectors

Computer Science II · Lecture 11 ·

A vector with size 3 and capacity 5 has two reserved slots.
A vector tracks its constructed elements separately from reserved storage. Adding an element increases its size.

Character searches return positions, while vectors store values at positions. Both require you to distinguish a value from an index and to handle the case where no usable element exists. Vectors also separate their logical size from allocated capacity, much like the earlier partially filled-array model.

Review CS1: string searches and CS1: records and vectors. This lecture brings their boundary and ownership rules together.

Search for any member of a character set #

find_first_of looks for the first character that belongs to a supplied set. It is not looking for that entire set as a consecutive substring. Supplying a set of vowels asks for the first occurrence of any listed vowel. find_first_not_of instead looks for the first character outside the set.[1]

std::string text = "  red";
auto start = text.find_first_not_of(" "); // 2

The string has spaces at indexes zero and one, followed by r at index two. The search skips each leading space, reaches r, and returns index two. It returns a position, not a new string and not the character itself.

If every character were a space, no qualifying character would exist, so the result would be std::string::npos. Check that outcome before creating a substring or indexing the result. An empty string also has no matching character. A starting-position argument can begin a search later in the string, but it does not remove the need for the not-found check.

The same process works for punctuation, digits, or separators: define the character set, identify whether membership or nonmembership is wanted, and interpret the result as either a valid position or failure.

Appending creates an element; indexing does not #

A vector owns an ordered sequence and knows how many elements currently exist.

std::vector<int> values;
values.push_back(4);
values.push_back(9);

The declaration creates an empty vector. After the first append, its size is one and four is at index zero. After the second append, its size is two and nine is at index one. Its last valid index is now one, not two.

Assigning to values[5] would not expand it. That expression attempts to change the sixth element, which this vector does not contain. push_back appends a new element. resize changes how many elements exist. The bounds-checked at accessor can report an invalid index instead of silently accepting unchecked indexing.

Size and capacity answer different questions #

Size counts the constructed elements. Capacity describes how many elements can fit in the current allocation before another allocation is needed. Reserving capacity requests storage without constructing those elements. A vector of size two with capacity ten still has only indexes zero and one available as elements.

Growth can reallocate: the vector moves its elements to new storage and releases the old storage. Pointers, references, and iterators into that previous storage may then become invalid. Keeping the vector object itself alive does not guarantee that a saved address to one of its elements remains valid after an append.[2]

Read constructor punctuation carefully #

std::vector<int> a(3); creates three value-initialized integers, all zero. The parentheses select a size construction here. std::vector<int> b{3}; creates one integer containing three, because the braces select the initializer-list constructor. std::vector<int> c{2, 4, 6}; creates the three listed elements.

That difference changes both size and values. Before indexing a newly constructed vector, read which constructor was selected rather than assuming every occurrence of three means three elements.

pop_back() removes the final element without returning its value. If you need that value, obtain it with back() first, then remove it. Both require a nonempty vector. Reducing size does not necessarily reduce capacity. Resizing upward constructs added elements, whereas reserving only makes room for later construction.

Passing a vector and visiting its elements #

Passing by value gives a function its own container copy. A mutable reference lets it change the caller's vector. const std::vector<int>& provides read-only access without copying the whole container. Make the intended effect part of the contract.

A range loop can visit copies, mutable references, or const references. Copies are independent loop-local values. Mutable references allow changes to stored elements. Const references avoid copies while preventing modification through that access path.

A function can return a vector as a value result, allowing construction of a collection without handing the caller a raw owning address. The caller still must validate later indexes and define what empty input means for its calculations.

Copying a container does not always copy targets #

For integer vectors, second = first copies the integer elements into separate destination storage. Later changing second[0] does not change first[0]. The vectors have independent element values.

For vectors of raw pointers, assignment copies the pointer values, which are addresses. The containers have separate pointer elements, but those elements can still lead to shared objects. This is aliasing of targets, not sharing the vector's own element storage.

A vector owns and cleans up its element storage. It does not automatically delete every allocation reachable through a raw pointer element. Dynamic-array ownership questions therefore remain relevant when pointer elements are involved, even though the container itself manages its own allocation.

Practice and explanation #

A vector has size two and reserved capacity eight. Can you assign index seven as an append? No. Append or resize must create the additional elements first. Capacity alone is not permission to access unconstructed positions.

Does copying a vector of raw pointers create independent copies of the pointed-to objects? No. It copies addresses. The two containers can still observe changes to the same shared targets.

References

  1. ↑ C++ working draft: string search .
  2. ↑ C++ working draft: vector size and capacity .