COMP6771 Week 2.1
STL Containers & Iterators
I/O (Input / Output) in C++
#include <iostream>
#include <fstream>
int i;
std::ifstream fin{"data.in"};
while (fin >> i) {
std::cout << i << "\n";
}
fin.close();
std::ofstream fout{"data.out"};
fout << i;
fout.close();
Better input stream example
#include <iostream>
#include <fstream>
int main () {
// Below line only works C++17
if (auto in = std::ifstream{"data.in"}; in) { // attempts to open file, checks it was opened
for (auto i = 0; in >> i;) { // reads in
std::cout << i << '\n';
}
if (in.bad()) {
std::cerr << "unrecoverable error (e.g. disk disconnected?)\n";
} else if (not in.eof()) {
std::cerr << "bad input: didn't read an int\n";
}
} // closes file automatically <-- no need to close manually!
else {
std::cerr << "unable to read data.in\n";
}
}
I/O (Input / Output) in C++
I/O (Input / Output) in C++
#include <iostream>
#include <iomanip> // to use the setprecision manipulator
int main() {
std::cout << 1331 << std::endl; // 1331
std::cout << "In hex " << std::hex << 1331 << std::endl; // In hex 533
std::cout << 1331.123456 << std::endl; // 1331.12
std::cout.setf(std::ios::scientific, std::ios::floatfield);
std::cout << 1331.123456 << std::endl; // 1.331123e+03
std::cout << std::setprecision(3) << 1331.123456 << std::endl; // 1.331e+03
std::cout << std::dec << 1331 << std::endl; // 1331
std::cout.fill(’X’);
std::cout.width(8);
std::cout << 1331 << std::endl; // XXXX1331
std::cout.setf(std::ios::left, std::ios::adjustfield);
std::cout.width(8);
std::cout << 1331 << std::endl; // 1331XXXX
}
Be careful using these, these are not the best style
Type Casting
- Types must be known at compile-time
- Type-checking happens at compile-time
std::string name = "Obama";
int age = 17;
age = name; // error
Type Conversions
- Implicit: Compiler-directed conversions
- Explicit: Programmer-specified conversions
// Implicit type conversion
#include <iostream>
int main() {
int age = 17;
double agePrecise = age;
std::cout << age;
}
Type Conversions - Explicit
- This is also known as static casting
double pi = 3.14;
int piInteger1 = pi; // What happens here?
int piInteger2 = (int)pi; // C-style
int piInteger3 = static_cast<int>(pi); // C++ style
int x = 5, y = 2;
// Note how the order of operations is not immediately obvious with C-style casts
double slope = (double)x / y; // C-style
double slope = static_cast<double>(x) / y; // C++ style
Iterating through arrays in C++
#include <array>
#include <iostream>
int main() {
// C-style. Don't do this
// int ages[3] = { 18, 19, 20 };
// for (int i = 0; i < 3; ++i) {
// std::cout << ages[i] << "\n";
// }
// C++ style. This can be used like any other C++ container.
// It has iterators, safe accesses, and it doesn't act like a pointer.
std::array<int, 3> ages{ 18, 19, 20 };
for (int i = 0; i < ages.size(); ++i) {
std::cout << ages[i] << "\n";
}
for (auto it = ages.begin(); it != ages.end(); ++it) {
std::cout << *it << "\n";
}
for (const auto& age : ages) {
std::cout << age << "\n";
}
}
Function Templates
- A function template is a prescription for the compiler to generate particular instances of a function varying by type
- A function template is not a function. It tells the compiler how to generate functions
- We will talk about this in a lot more detail in later weeks
template <typename T>
T Min(T a, T b) {
return a < b ? a : b;
}
int main() {
Min(1, 2); // uses int min(int, int);
Min(1.1, 2.2); // double min(double, double);
}
STL: Standard Template Library
- STL is an architecture and design philosophy for managing generic and abstract collections of data with algorithms
- All components of the STL are templates
- Containers store data, but don't know about algorithms
- Iterators are an API to access items within a container in a particular order, agnostic of the container used
- Each container has its own iterator types
- Algorithms manipulate values referenced by iterators, but don't know about containers
Container
Container
Algorithm
Container
Iterator
Iterator
STL: Iterators
- Iterator is an abstract notion of a pointer
- Iterators are types that abstract container data as a sequence of objects
- The glue between containers and algorithms
STL: Iterators
- a.begin(): abstractly "points" to the first element
-
a.end(): abstractly "points" to one past the last element
- a.end() is not an invalid iterator value
- If iter abstractly points to the k-th element, then:
- *p is the object it abstractly points to
- ++p abstractly points to the (k + 1)-st element
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> names;
for (auto iter = names.begin(); iter != names.end(); ++iter) {
std::cout << *iter << "\n";
}
for (std::vector<std::string>::iterator iter = names.begin(); iter != names.end(); ++iter) {
std::cout << *iter << "\n";
}
}
Iterators, Constness, Reverse
#include <iostream>
#include <vector>
int main() {
std::vector<int> ages;
ages.push_back(18);
ages.push_back(19);
ages.push_back(20);
// type of iter would be std::vector<int>::iterator
for (auto iter = ages.begin(); iter != ages.end(); ++iter) {
(*iter)++; // OK
}
// type of iter would be std::vector<int>::const_iterator
for (auto iter = ages.cbegin(); iter != ages.cend(); ++iter) {
//(*iter)++; // NOT OK
}
// type of iter would be std::vector<int>::reverse_iterator
for (auto iter = ages.rbegin(); iter != ages.rend(); ++iter) {
std::cout << *iter << "\n"; // prints 20, 19, 18
}
// Can also use crbegin and crend
}
Iterator Categories
Operation | Output | Input | Forward | Bidirectional | Random Access |
---|---|---|---|---|---|
Read | =*p | =*p | =*p | =*p | |
Access | -> | -> | -> | -> [] | |
Write | *p= | *p= | *p= | *p= | |
Iteration | ++ | ++ | ++ | ++ -- | ++ -- + - += -= |
Compare | == != | == != | == != | == != < > <= >= |
More powerful
Input
Output
Forward
Bidir.
Random
"->" no longer specified as of C++20
Iterator Categories
An algorithm requires certain kinds of iterators for their operations
- input: find(), equal()
- output: copy()
- forward: replace(), binary_search()
- bi-directional: reverse()
- random: sort()
A container's iterator falls into a certain category
- forward: forward_list
- bi-directional: map, list
- random: vector, deque
stack, queue are container adapters, and do not have iterators
Other Iterators: Streams
#include <fstream>
#include <iostream>
#include <iterator>
int main() {
std::ifstream in("data.in");
std::istream_iterator<int>begin(in);
std::istream_iterator<int> end;
std::cout << *begin++ << "\n"; // read the first int
++begin; // skip the 2nd int
std::cout << *begin++ << "\n"; // read the third int
while (begin != end) {
std::cout << *begin++ << "\n"; // read and print the rest
}
}
STL: Containers
- STL containers are abstractions of common data structures
- cppreference has a summary of them here.
- Different containers have different time complexity of the same operation (see right)
O(1)+ means amortised constant time
Sequential Containers
- Elements have specific order controlled by programmer
- These are all templates (but class templates, not function templates)
- Note that vector is still your go-to type for this sort of data. It is typically faster, so don't use another type without a good reason.
Sequential container | Description |
---|---|
std::vector | Dynamically sized array |
std::list | Doubly linked list |
std::forward_list | Singly linked list |
std::deque | <vector> with fast operations for element at beginning |
std::array | C-style array wrapper |
Vector (Container)
- Array-like container most used is <vector>
- Abstract, dynamically resizable array
- In later weeks we will learn about various ways to construct a vector
#include <iostream>
#include <vector>
// Begin with numbers 1, 2, 3 in the list already
int main() {
// In C++17 we can omit the int if the compiler can determine the type.
std::vector<int> numbers {1, 2, 3};
int input;
while (std::cin >> input) {
numbers.push_back(input);
}
std::cout << "1st element: " << numbers.at(0) << "\n"; // slower, safer
std::cout << "2nd element: " << numbers[1] << "\n"; // faster, less safe
std::cout << "Max size before realloc: " << numbers.capacity() << "\n";
for (int n : numbers) {
std::cout << n << "n"
}
}
Associative Containers
- A value type is accessed through a second data type, the key.
- Associative containers include:
- map<T>
- log(n) for most operations, probably stored as a red-black tree
- Ordered by key value (requires key to be comparable with <)
- Iterators will iterate through in order of key, not by insertion time
- unordered_map<T>
- O(1) for most operations
- Stored as a hash table (requires keys to be hashable)
- Iterators will iterate through in an arbitrary, undefined order
- set<T>
- Search, removal, insertion have log(n) complexity
- Contains sorted set of unique objects of type Key
- map<T>
std::map example
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, double> m;
// The insert function takes in a key-value pair.
std::pair<std::string, double> p1{"bat", 14.75};
m.insert(p1);
// The compiler will automatically construct values as
// required when it knows the required type.
m.insert({"cat", 10.157});
// This is the preferred way of using a map
m.emplace("cat", 10.157);
// This is very dangerous, and one of the most common causes of mistakes in C++.
std::cout << m["bat"] << '\n';
auto it = m.find("bat"); // Iterator to bat if present, otherwise m.end()
// This is a great example of when to use auto, but we want to show you what type it is.
for (const std::pair<const std::string, double>& kv : m) {
std::cout << kv.first << ' ' << kv.second << '\n';
}
}
COMP6771 19T2 - 2.1 - STL Containers and iterators
By cs6771
COMP6771 19T2 - 2.1 - STL Containers and iterators
- 1,798