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:
Do not know their own size
Need to have their size specified when declared
Decay into pointers easily
Provide no convenience functions
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>.
Declare
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
Run It
1#include <iostream>
2#include <string>
3#include <vector>
4
5using std::vector; // alias type std::vector
6
7int main() {
8 vector<int> x; // empty vector of int
9
10 for (const auto& value: x)
11 {
12 std::cout << value << ',';
13 }
14 std::cout << '\n';
15
16 vector<std::string> dos_equis (2, "x"); // "x", "x"
17
18 vector<int> pi_digits = {3,1,4,1,5,9}; // C++11
19 for (const auto& value: pi_digits)
20 {
21 std::cout << value << ',';
22 }
23 std::cout << '\n';
24
25 return 0;
26}
Given a vector declared as:
std::vector<int> v(4);
A container capable of storing 4 integers is created:
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.
Access operations
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?
Run It
This example demonstrates an out of range error.
How can we fix this while changing the least amount of code possible?
1#include <iostream>
2#include <vector>
3
4int main() {
5 std::vector<int> numbers {2, 4, 6, 8};
6 std::cout << "Size: " << numbers.size() << '\n';
7 std::cout << "Second element: " << numbers[1] << '\n';
8
9 numbers.at(0) = 5;
10 numbers.at(4) = numbers[3] + 2; // out of range error.
11 // index 4 is out of bounds
12
13 std::cout << "All numbers:";
14 for (const auto& num : numbers) {
15 std::cout << ' ' << num;
16 }
17 std::cout << '\n';
18 return 0;
19}
Fill and print vector
A vector 'hello world':
Fill a vector in one simple way, using push_back
iterate through the vector and print
1#include <iostream>
2#include <string>
3#include <vector>
4
5using std::cout;
6using std::string;
7using std::vector;
8
9
10int main() {
11 vector<string> words = {
12 "reach", "clear", "fall", "set", "yard",
13 "liquid", "wise", "badge", "four", "coherent"
14 };
15
16 cout << "Word list: \n";
17 for (string s: words) {
18 cout << s << ", \thas " << s.size() << " letters\n";
19 }
20}
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
trueif 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.
Compare operations
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;
Run It
1#include <vector>
2#include <iostream>
3
4int main() {
5 std::vector<int> x {2, 4, 6, 8};
6 std::vector<int> y;
7
8 if (x == y) {
9 std::cout << "x and y are equal\n";
10 } else {
11 std::cout << "x and y differ\n";
12 }
13
14 y = x; // copy all data from x into y
15 if (x == y) {
16 std::cout << "x and y are equal\n";
17 } else {
18 std::cout << "x and y differ\n";
19 }
20
21 return 0;
22}
Try This!
Create two vectors of strings containing the same values and check them for equality.
1#include <iostream>
2
3int main() {
4}
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:
values.push_back(40);
Appends the value 40 to the end of the vector.
push_back and pop_back
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:
Run It
1#include <vector>
2#include <iostream>
3
4int main() {
5 std::vector<char> letters {'a', 'b', 'c'};
6
7 letters.at(0) = 'z';
8 letters.push_back('d'); // add 'd' to the end of the vector
9 char ch = 'e';
10 letters.push_back(ch); // add 'e' to the end
11 letters.pop_back(); // pop_back is the opposite:
12 // - removes the end element from the vector
13
14 std::cout << "All letters:";
15 for (const auto& c : letters) {
16 std::cout << ' ' << c;
17 }
18 std::cout << '\n';
19 letters.clear(); // clear all contents from vector
20 return 0;
21}
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.
Managing the storage capacity in addition to the vector data is one of the things that make vectors efficient.
Phrase O'matic
The 'phrase-o-matic' is a port of a fun little java program from Head First Java, 2nd ed. ISBN-13: 978-0596009205
1#include <iostream>
2#include <random>
3#include <string>
4#include <vector>
5
6int main() {
7 // initialize a random number generator
8 std::random_device r;
9 std::default_random_engine eng(r());
10 using rand = std::uniform_int_distribution<std::uint64_t>;
11
12 const char* list_one[] = {
13 "24/7", "multi-tier", "30,000 foot", "B-to-B", "win-win",
14 "front-end", "web-based", "pervasive", "smart", "six-sigma",
15 "critical-path", "dynamic", "extreme", "three-tier", "agile"
16 };
17
18 // we prefer vector over arrays
19 const std::vector<std::string> list_two = {
20 "empowered", "sticky", "value-added", "oriented", "centric",
21 "distributed", "clustered", "branded", "outside-the-box",
22 "positioned", "networked", "focused", "leveraged", "aligned",
23 "targeted", "shared", "cooperative", "accelerated"
24 };
25
26 const std::vector<std::string> list_three = {
27 "process", "tipping-point", "solution", "architecture",
28 "core competency", "strategy", "mind-share", "portal",
29 "space", "vision", "paradigm", "mission"
30 };
31
32 const std::size_t one_size = 14; // arrays don't know their size
33 auto r1 = rand {0, one_size} (eng);
34 auto r2 = rand {0, list_two.size()-1} (eng); // vectors know their size
35 auto r3 = rand {0, list_three.size()-1} (eng);
36
37 std::string phrase = {list_one[r1]};
38 phrase += " " + list_two[r2] + " " + list_three[r3];
39
40 std::cout << "What we need is a " << phrase << '\n';
41
42 // or could have omitted temporary phrase and simply:
43 // std::cout << "What we need is a " << list_one[r1] << ' '
44 // << list_two[r2] << ' '
45 // << list_three[r3] << '\n';
46 return 0;
47}
vector test
A short test program to demonstrate the parts of the vector interface.
What other tests can you make?
1#include <iomanip>
2#include <iostream>
3#include <string>
4#include <vector>
5
6using std::string;
7using std::vector;
8
9vector<string> make_vector() {
10 return {
11 "reach", "clear", "fall", "set", "yard",
12 "liquid", "wise", "badge", "four", "coherent"
13 };
14}
15
16void check (const string& name, const string& actual, const string& expected)
17{
18 std::cout << std::left << std::setfill('.')
19 << std::setw(50) << name
20 << std::setw(7) << std::left;
21 if(actual == expected) {
22 std::cout << " OK \n";
23 return;
24 }
25 std::cout << " FAILED\n";
26 std::cout << "\treceived [" << actual
27 << "], but expected [" << expected << "]\n";
28 exit(1);
29}
30
31// write test cases
32void test_simple_access(vector<string> words) {
33 check("test at()", words.at(0), "reach");
34 check("test operator[]", words[0], "reach");
35 check("test at() end", words.at(9), "coherent");
36 check("test operator[]", words[9], "coherent");
37
38 // foo.at(i) and foo.[i] refer to the same thing
39 for (auto i=0U; i< 3; ++i) {
40 // for (auto i=0U; i< words.size(); ++i) {
41 check("at and operator[] are the same", words.at(i), words[i]);
42 }
43
44 const string empty;
45 // Try uncommenting these lines to see what happens
46 //check("test at out of bounds", words.at(-1), empty);
47 //check("test at out of bounds", words.at(11), empty);
48 //check("test [] out of bounds", words[-1], empty);
49 //check("test [] out of bounds", words[11], empty);
50}
51
52void test_other_access(vector<string> words) {
53 check("test front()", words.front(), words.at(0));
54 check("test back()", words.back(), words.at(words.size()-1));
55}
56
57void test_assignment(vector<string> words) {
58 check("test original value", words[2], "fall");
59 words[2] = "falldown";
60 check("test new value", words[2], "falldown");
61}
62
63// call test cases
64int main() {
65 auto words = make_vector();
66
67 test_simple_access(words);
68 test_other_access(words);
69 test_assignment(words);
70}
More to Explore
cppreference.com std::vector
WikiBooks.org C++ Programming STL Containers