2.6. The vector class

A vector is intended to behave like a dynamically sized array. It is a template, so unlike a string, which is a container for characters only, a vector can serve as a container for any type. More on templates later, for now, we just need to know enough to know how declare a vector.

As with strings, in standard C, the typical way to work with a collection of data is with a ‘raw’ array:

int a[] = {3, 1, 4, 1, 5, 9};

Some downsides to raw arrays are that they:

The vector class solves these problems for us and a few others besides. Declaring a vector is quite similar to the string declarations from the previous section. In order to access the STL vector capabilities, use #include <vector>.

To properly declare a vector, the type of data stored in the vector must be declared as a type parameter.

The <int> and <std::string> represent the template parameters passed to the vector. It is these template parameters that allow the vector class to serve as a container for (almost) any type. There are some limits we will cover later, but for now, know that any normal type you already have learned about can be stored in a vector.

// an empty vector of int
vector<int> x;

// initialize and store: "x", "x"
vector<std::string> dos_equis (2, "x");

// C++11 initialization list syntax
vector<int> pi_digits = {3,1,4,1,5,9};

Unlike a fundamental type, the declaration vector<int> x; does not create an uninitialized variable. It creates a fully formed vector with no elements stored in it yet. This is perfectly OK and normal.

However, a common error is to forget to include the template parameter:

vector x;        // compile error

Given a vector declared as:

std::vector<int> v(4);

A container capable of storing 4 integers is created:

digraph {
node [
     fontname = "Courier"
     fontsize = 14
     shape = "record"
     style=filled
     fillcolor=lightblue
  ]
  names [
     color = white;
     fillcolor=white;
     label = "{size: | <f0> data: }";
  ]
  struct [
     label = "{4 | <f0> }";
  ]
  node [shape=record, color=black, fontcolor=black, fillcolor=white, width=3.75, fixedsize=true];
  labels [label="<f0> | <f4> size", color=white];
  values [label="<f0> v[0] | <f1> v[1] | <f2> v[2] | <f3> v[3]",
          color=black, fillcolor=lightblue, style=filled];
  edge [color=black];
  struct:f0:s -> values:f0;
  labels:f4 -> values:f3;
  {rank=same; struct,labels};
}

Although the vector object is initialized, its contents are not. Many compiler implementations will initialize the contents to zero, but don’t rely on this behavior. Explicitly initialize with a default value, if that is what you want:

std::vector<int> v(4, -1);

A vector comes with a rich assortment of convenience functions. Like an array, the operator[] can be used to access elements without bounds checking. Like a string, the at function provides bounds checking and will throw a std::out_of_range exception if an out of bounds index is used on the vector.

Like arrays, indexes are zero-based.

// read vector elements
std::cout << "First element: " << numbers[0];
std::cout << "First element: " << numbers.at(0);

// write vector elements
numbers[0] = 5;
numbers.at(0) = 5;

A common source of error occurs when printing a vector. A vector feels like a built-in type and this seems like it should work:

// compile error
std::cout << "all numbers: " << numbers;

The vector type does not ‘know’ how to send it’s values to an output stream by default.

Something to consider

Why do you think this feature is not built into the standard library?

This example demonstrates an out of range error.

How can we fix this while changing the least amount of code possible?

A vector ‘hello world’:

  1. Fill a vector in one simple way, using push_back

  2. iterate through the vector and print

The vector class also provides:

front and back

return a reference to the first and last elements

size

return the number of elements

empty

return true if the container is empty

Although there are more functions, these are the ones we need to worry about for now. We will be looking more at memory management in vectors in The std::vector class.

Something to consider

What is the difference between a std::string and std::vector<char>?

Why did the developers of the STL decide it was important to include both?

Comparisons between vectors are also automatically handled by the class. In the case of a vector, operator==, or an equality comparison between two vectors a and b, means the two vectors are equal if a.size() == b.size() and each element in a compares equal with each element in b in the same position in the vector.

Vectors support the same syntax as the built in types.

// declare 2 vectors, one empty and one not

std::vector<int> x {2, 4, 6, 8};
std::vector<int> y;

bool test = (x == y);   // test is false

y = x;

Try This!

Create two vectors of strings containing the same values and check them for equality.

2.6.1. Adding data to a vector

How do we solve the out_of_range exception from a few examples ago? How do we dynamically add data to a vector?

A simple way is to use the push_back function.

Given an vector of 3 int’s:

digraph {
node [
     fontname = "Courier"
     fontsize = 14
     shape = "record"
     style=filled
     fillcolor=lightblue
  ]
  names [
     color = white;
     fillcolor=white;
     label = "{size: | <f0> data: }";
  ]
  struct [
     label = "{3 | <f0> }";
  ]
  node [shape=record, color=black, fontcolor=black, fillcolor=white, width=3.75, fixedsize=true];
  labels [label="<f0> | <f2> size", color=white];
  values [label="<f0> v[0]\n= 10 | <f1> v[1]\n= 20 | <f2> v[2]\n= 30",
          color=black, fillcolor=lightblue, style=filled];
  edge [color=black];
  struct:f0:s -> values:f0;
  labels:f2 -> values:f2;
  {rank=same; struct,labels};
}
values.push_back(40);

Appends the value 40 to the end of the vector.

digraph {
node [
     fontname = "Courier"
     fontsize = 14
     shape = "record"
     style=filled
     fillcolor=lightblue
  ]
  names [
     color = white;
     fillcolor=white;
     label = "{size: | <f0> data: }";
  ]
  struct [
     label = "{4 | <f0> }";
  ]
  node [shape=record, color=black, fontcolor=black, fillcolor=white, width=3.75, fixedsize=true];
  labels [label="<f0> | <f4> size", color=white];
  values [label="<f0> v[0]\n= 10 | <f1> v[1]\n= 20 | <f2> v[2]\n= 30 | <f3> v[3]\n= 40",
          color=black, fillcolor=lightblue, style=filled];
  edge [color=black];
  struct:f0:s -> values:f0;
  labels:f4 -> values:f3;
  {rank=same; struct,labels};
}

push_back appends an element to the end and increases the capacity of the vector, if needed.

pop_back reduces the size of the vector by one. The last element is no longer available.

Note that pop_back does not return a value.

If you need that last element, remember to save it first.

std::vector<char> letters {'a', 'b', 'c'};

letters.push_back('d');  // add 'd' to the end of the vector
letters.pop_back();      // pop_back is the opposite:

2.6.2. Vector capacity

A vector exposes an interface that ‘feels like’ an array, but the underlying storage grows to accommodate new data as required. With an array, you either have to allocate as much memory as you might need in the worst case, even if only a small fraction is used most of the time or you allocate ‘just enough’ and when more memory is required, copy all the data into a new array. A vector does do this also, but the implementation is hidden and you don’t have to worry about it.

Something to be aware of - when pop_back is called, no actual storage is deleted. The memory is still available in the vector and available for reassignment with pop_back.

This extra memory after the current size is referred to as the total capacity of the vector, or just capacity.

digraph {
node [
     fontname = "Courier"
     fontsize = 14
     shape = "record"
     style=filled
     fillcolor=lightblue
  ]
  names [
     color = white;
     fillcolor=white;
     label = "{size: | <f0> data: }";
  ]
  struct [
     label = "{4 | <f0> }";
  ]
  node [shape=record, color=black, fontcolor=black, fillcolor=white, width=3.75, fixedsize=true];
  labels [label="<f0> | <f4> size | <f5> spare\ncapacity ", color=white];
  values [label="<f0> v[0]\n= 1 | <f1> v[1]\n= 1 | <f2> v[2]\n= 1 | <f3> v[3]\n= 1 |     | <f5>   ",
          color=black, fillcolor=lightblue, style=filled];
  edge [color=black];
  struct:f0:s -> values:f0;
  labels:f4 -> values:f3;
  labels:f5 -> values:f5;
  {rank=same; struct,labels};
}

Managing the storage capacity in addition to the vector data is one of the things that make vectors efficient.

The ‘phrase-o-matic’ is a port of a fun little java program from Head First Java, 2nd ed. ISBN-13: 978-0596009205

A short test program to demonstrate the parts of the vector interface.

What other tests can you make?


More to Explore

You have attempted of activities on this page