CS106L
Lec0: Introduction
c++
qt
Lec1: Streams_I
The idea behind streams
A stream is an abstraction for input/output
You can write any particular type to stream
input from user as string
output to user as string
compute as object
Types of streams
==output streams==: can only receive data
insertion operator: <<
<<
converts data to string and sends to stream
==input streams==: can only give you data
extraction operator: >>
>>
gets data from stream as a string and converts it into the appropriate type
4 | 2position1 |
1 | 3 | 4position2 |
\nposition3 |
---|
- position1: Extracting an integer will read as many characters as possible from the stream.
- position2: Extracting again will skip over any whitespace when reading the next integer.
- position3: When no more data is left, the fail bit will be set to true and input.fail() will return true.
Reading into a string using >> will only read a single word, not the whole line.
To read a whole line, use
getline(istream& stream, string& line)
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 static void readHaikuLine() {
// Create our ifstream and make it open the file
std::ifstream input("../res/haiku.txt");
// This will store the lines as we get them form the stream
string line;
while(true) {
std::getline(input, line);
// If input is in a fail state, either a value couldn't
// be converted, or we are at the end of the file.
if(input.fail())
break;
cout << line << endl;
}
}haiku.txt:
1
2
3 Space is limited
Haiku make it difficult
To finish what yououtput:
1
2
3
4
5
6
7
8
9
10
11 Word read: Space
Word read: is
Word read: limited
Word read: Haiku
Word read: make
Word read: it
Word read: difficult
Word read: To
Word read: finish
Word read: what
Word read: younext let’s do some tests on cin and cout
1
2
3
4
5
6 int main() {
char a;
cin >> a;
cout << a;
return 0;
}input:
41
output:
4
1
2
3
4
5
6 int main() {
char a;
cin >> a;
cout << a+1;
return 0;
}input:
41
output:
53
the ascii code of 1 is 49(0d)
Lec2: Streams_II
Stream Internals
Writing to a console/file is a slow operation
So we can use a temporary buffer/array to accumulate characters
When buffer is full, write out all contents of the buffer to the output device at once.
↑ This process is flushing the stream
The internal sequence of data stored in a stream is called a buffer.
Istreams use them to store data we haven’t used yet.
Ostreams use them to store data they haven’t passed along yet.
==Flushing the Buffer==
if we want to force the contents in buffer to their destination, we can flush.
1
2
3
4
5 stream.flush(ch); // use by default
stream << std::flush; // use if you are printing
stream << std::endl; // use if you want a newlinethe endl just newline and flush
1 stream << "\n" << std::flush;
Streams have four bits to give us information about their state:
- Good bit
- EOF bit
- Fail bit
- Bad bit
Stream Shortcuts
The << and >> operators are not magic, they are actually functions!
The << and >> operators return the stream passed as their left argument.
1
2
3
4
5
6 std::cout << "hello" << 23 << "world";
(((std::cout << "hello") << 23) << "world");
(((std::cout) << 23) << "world");
((std::cout << 23) << "world");
((std::cout) << "world");
(std::cout << "world");
Stream Manipulators
Common:
● endl inserts a newline and flushes the stream
● ws skips all whitespace until it finds another char
● boolalpha prints “true” and “false” for bools
Numeric:
● hex: prints numbers in hex
● setprecision: adjusts the precision numbers print with
Padding:
● setw pads output
● setfill fills padding with character
StringStream
Sometimes we want to be able to treat a string like a stream.
Useful scenarios:
● Converting between data types
● Tokenizing a string
Lec3: Sequential Containers
getline() vs cin
using
getline()
aftercin
1
2
3
4
5
6
7
8 int number;
string name;
cin >> number;
cout << "number is: " << number << endl;
getline(cin,name);
cout << "name is: " << name;
input:
21/n
output:
21
number is: 21
name is:
the reason why i cannot input name is that there already have ‘/n’ character in istream so getline() will identify it and end.
solution:
using
cin >> ws
to ignore orcin.ignore(a,h)
Useful Aside
struct
STL
Sequence Containers
- std::vector<T>
- std::list<T>
- std::deque<T>
==vector==
some tips
1
2
3
4
5 const int kNumInts = 5;
// const is a promise to the compiler that this variable won’t change.
using vecsz_t = std::vector<int>::size_type;
// using let’s us use vecsz_t as an alias/synonym for the type std::vector<int>::size_type;
std::sort(vec.begin(), vec.end());vectors only grow efficiently in one direction
push_front()
is impossible
==deque==
double ended queue, fast
push_front()
one common implementation:
So If deque can do everything a vector can do and also has a fast push_front…
Why use a vector at all?
Deques support fast push_front operations. However, for other common operations like element access, vector will always outperform a deque.
implementation of stacks and queue using vector or deque
push_back()
push_back()
pop_back()
pop_front()
Lec4: Associative Containers and Iterators
Associative Containers
access data by key not index
● std::map<T1, T2>
● std::set
● std::unordered_map<T1, T2>
● std::unordered_set
Iterators
1
2
3
4
5
6
7
8
9 //for (int i = 0; i < ???; i++)
// we don't know the i should less than what if we want to iterate over the associative container
set<int> mySet;
iterator iter = mySet.begin();
while(iter != mySet.end()) {
cout << *iter << endl;
++iter;
}
return;● Create iterator
● Dereference iterator to read value currently pointed to
● Advance iterator
● Compare against another iterator (especially .end() iterator)
Lec5: Advanced Associative Containers
Further Usage
Sorting
1 std::sort(vec.begin(), vec.end());Finding elements
1
2
3
4
5
6 set<int>::iterator i = mySet.lower_bound(7);
set<int>::iterator end = mySet.lower_bound(26);
while (i != end) {
cout << *i << endl;
++i;
}for [a,b]
lower_bound(a)
is >= aupper_bound(b)
is <= b
Multimap
we want to map the same key to different values
1
2
3
4 multimap<int, int> myMMap;
myMMap.insert(make_pair(3, 3));
myMMap.insert({3, 12}); // shorter syntax
cout << myMMap.count(3) << endl; // prints 2
auto
just think the map that store the deque of string and vector of string
map<deque<string>, vector<string>>
if we want to get its iterator
1 map<deque<string>, vector<string>>::iterator iter = map.begin()it’s too tedious
so we want to clean this up, use alias ?
1 using NGramMap = map<deque<string>, vector<string>>;can we do better? Yes, the keyword
auto
1
2
3
4 map<deque<string>, vector<string>> myMap;
for(auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
doSomething(*(iter).first, *(iter).second);
}A range based for loop is (more or less) a shorthand for iterator code:
1
2
3
4 map<string, int> myMap;
for(auto thing : myMap) {
doSomething(thing.first, thing.second);
}
1
2
3
4
5 map<string, int> myMap;
for(auto iter = myMap. begin(); iter != myMap. end(); ++iter) {
auto thing = *iter;
doSomething (thing.first, thing.second);
}
Lec6: Templates and Iterators
Templates
concern the function that choose the less among two numbers
1
2
3
4
5
6 int min(int a, int b) {
return (a < b) ? a : b;
}
min(3,5); // 3
min(13,8); // 8
min(1.9,3.7); // ?so we should write a new function for the double_size
1
2
3 double min(double a, double b) {
return (a < b) ? a : b;
}and the siez_t, float, char…?
1
2
3
4
5
6
7
8
9 size_t min_sizet(size_t a, size_t b) {
return (a < b) ? a : b;
}
float min_float(float a, float b) {
return (a < b) ? a : b;
}
char min_ch(char a, char b) {
return (a < b) ? a : b;
}it’s too tedious!
- Multiple copies of essentially the same function.
- You need to write the type name whenever you use it
- Every time you want to add a new type, you need to add a new function.
- If you edit the function slightly, you need to edit it in each version manually
we can use overloaded functions to avoid one of these problems
1
2
3
4
5
6
7
8 int min(int a, int b) {
return (a < b) ? a : b;
}
double min(double a, double b) {
return (a < b) ? a : b;
}
min(3,5); // 3
min(1.9, 5.7); // 1.9
- Multiple copies of essentially the same function.
You need to write the type name whenever you use it- Every time you want to add a new type, you need to add a new function.
- If you edit the function slightly, you need to edit it in each version manually
The solution: Templates
Templates are a blueprint of a function that let you use the same function for a variety of types:
1
2
3
4
5 template <typename T>
T min(T a, T b) {
return (a < b) ? a : b;
}
int c = min(3,5); // 3
Implicit Interface
Any time template instantiation occurs, the compiler will check that all operations used on the templatised type are supported by that type.
What types are valid to use with a templatized function?
Any that satisfy its implicit interface.
1
2
3
4
5
6
7
8
9 template <typename T>
int foo(T input) {
int i;
if(input >> i && input.size() > 0) {
input.push_back(i); return i;
} else {
return 5;
}
}
Templates ft. Iterators
Every different collection comes equipped with its own type of iterator:
1
2
3
4
5
6 vector<int> v;
vector<int>::iterator itr = v.begin();
vector<double> v;
vector<double>::iterator itr = v.begin();
deque<int> d;
deque<int>::iterator itr = d.begin();The whole point of iterators was to have a standard interface to iterate over data in any container.
But we still had to specify what type of data this iterator was pointing to.
We want to ultimately write generic functions to work with iterators over any sequence.
With templates we can!
Lec7: Algorithms
Iterator Types
So far we have only really incremented iterators
But for some containers, we should be able to jump anywhere
1
2
3
4
5 std::vector<int> v(10);
auto mid = v.begin() + v.size()/2;
std::deque<int> d(13);
auto some_iter = d.begin() + 3;What about std::list(dllist)?
1
2 std::list<int> myList(10);
auto some_iter = myList.begin() + 3;There are 5 different types of iterators
Input
Output
Forward
Bidirectional
Random access
All iterators share a few common traits:
- Can be created from existing iterator
- Can be advanced using ++
- Can be compared with == and !=
input and output are single-pass input
input can only be dereferenced on right side of expression
1 int val = *itr;output can only be dereferenced on left side of expression
1 *itr = 12;forward is same as input/output except can make multiple passes
1
2
3
4 vector<int> v = ...
vector<int>::iterator itr = v.begin();
int val = *itr;
int val2 = *itr;bidirectional is same as forward except can also go backwards with decrement operator –
1
2
3
4
5
6 vector<int> v = ...
vector<int>::iterator itr = v.begin();
++itr;
int val = *itr;
--itr;
int val2 = *itr;Random access is same as bidirectional except can be incremented or decremented by arbitary amounts using + and -
1
2
3
4
5 vector<int> v = ...
vector<int>::iterator itr = v.begin();
int val = *itr;
itr = itr + 3;
int val2 = *itr;
Algorithms
Abstraction in the STL
Basic Types → Containers → Iterators → Algorithms
Let’s look at the std::copy algorithm to get a better understanding of algorithms and iterators:
1
2
3 vector<int> v {561, 1105, 1729, 2465};
vector<int> vCopy(v.size());
std::copy(v.begin(), v.end(), vCopy.begin());copy begin at vCopy.begin from v.begin to v.end
but what happen if vCopy don’t have enough space?
1
2
3 vector<int> v {561, 1105, 1729, 2465};
vector<int> vCopy(2);
std::copy(v.begin(), v.end(), vCopy.begin());We want to be able to copy into a collection by inserting into it, rather than making space for it first.
Iterator Adapters
Sometimes we need to form “weird” iterators:
- Iterating over streams would be pretty cool
- Having an iterator that could “insert” into a collection would be pretty cool
std::ostream_iterator
Look like output iterators
- Can be dereferenced with *
- Can be advanced with ++
Whenever you dereference a
std::ostream_iterator
and assign a value to it, the value is printed to a specifiedstd::ostream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 void printVector(vector<int>& nums) {
cout << "{";
for (auto elem : nums) {
cout << elem << ", ";
}
cout << "}" << endl;
}
int main() {
std::ostream_iterator<int> iter(cout, ", ");
*iter = 15;
++iter;
*iter = 1;
cout << endl;
vector<int> vec{3,1,4,1,5,9,2,6,5,3};
printVector(vec);
// full type is annoying to write so we'll use auto
// std::back_insert_iterator<vector<int>> it = std::back_inserter(vec);
auto it = std::back_inserter(vec);
*it = 5;
++it;
*it = 12;
printVector(vec);
}run and we will get these
1
2
3 15, 1,
{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, }
{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 12, }Yes, it looks like you’re manipulating contents of a container, but really you’re writing characters to the cout stream.
1
2 std::vector<int> v{3, 1, 4, 1, 5};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(cout, ", "))So we can solve the problem in last paragraph:
We want to be able to copy into a collection by inserting into it, rather than making space for it first.
The standard library provides insert iterators(
std::inserter
std::back_inserter
std::front_inserter
)
1
2
3 vector<int> v {561, 1105, 1729, 2465};
vector<int> vCopy;
std::copy(v.begin(), v.end(), std::back_inserter(vCopy));